From 1134da7ed371abdec2ce43af7c65db4f8bce8a3f Mon Sep 17 00:00:00 2001 From: Luciabrightcode Date: Mon, 22 Dec 2025 18:01:15 +0800 Subject: [PATCH] feat: complete Phase 2 core components (Camoufox & CurlClient) --- requirements.txt | 6 +- .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 147 bytes .../__pycache__/manager.cpython-310.pyc | Bin 0 -> 4112 bytes .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 149 bytes .../__pycache__/client.cpython-310.pyc | Bin 0 -> 2529 bytes src/extractor/client.py | 2 +- tests/__pycache__/__init__.cpython-310.pyc | Bin 0 -> 141 bytes ...test_handover.cpython-310-pytest-7.4.3.pyc | Bin 0 -> 3190 bytes ...test_handover.cpython-310-pytest-9.0.2.pyc | Bin 0 -> 3190 bytes venv/bin/curl-cffi | 8 + venv/bin/pygmentize | 8 + .../__pycache__/py.cpython-310.pyc | Bin 325 -> 400 bytes .../site-packages/_pytest/__init__.py | 8 +- .../__pycache__/__init__.cpython-310.pyc | Bin 364 -> 427 bytes .../__pycache__/_argcomplete.cpython-310.pyc | Bin 4055 -> 4124 bytes .../__pycache__/_version.cpython-310.pyc | Bin 500 -> 660 bytes .../__pycache__/cacheprovider.cpython-310.pyc | Bin 19560 -> 21035 bytes .../__pycache__/capture.cpython-310.pyc | Bin 34759 -> 37358 bytes .../__pycache__/compat.cpython-310.pyc | Bin 11498 -> 9847 bytes .../__pycache__/debugging.cpython-310.pyc | Bin 11528 -> 11699 bytes .../__pycache__/deprecated.cpython-310.pyc | Bin 4303 -> 2849 bytes .../__pycache__/doctest.cpython-310.pyc | Bin 23134 -> 22799 bytes .../__pycache__/faulthandler.cpython-310.pyc | Bin 2714 -> 3370 bytes .../__pycache__/fixtures.cpython-310.pyc | Bin 46184 -> 57102 bytes .../freeze_support.cpython-310.pyc | Bin 1415 -> 1451 bytes .../__pycache__/helpconfig.cpython-310.pyc | Bin 7574 -> 8718 bytes .../__pycache__/hookspec.cpython-310.pyc | Bin 33305 -> 44033 bytes .../__pycache__/junitxml.cpython-310.pyc | Bin 21123 -> 21320 bytes .../__pycache__/legacypath.cpython-310.pyc | Bin 17840 -> 17978 bytes .../__pycache__/logging.cpython-310.pyc | Bin 30061 -> 31616 bytes .../_pytest/__pycache__/main.cpython-310.pyc | Bin 23363 -> 30014 bytes .../__pycache__/monkeypatch.cpython-310.pyc | Bin 12546 -> 13061 bytes .../_pytest/__pycache__/nodes.cpython-310.pyc | Bin 22690 -> 23500 bytes .../_pytest/__pycache__/nose.cpython-310.pyc | Bin 1444 -> 0 bytes .../__pycache__/outcomes.cpython-310.pyc | Bin 9893 -> 10095 bytes .../__pycache__/pastebin.cpython-310.pyc | Bin 3570 -> 3811 bytes .../__pycache__/pathlib.cpython-310.pyc | Bin 21846 -> 29116 bytes .../__pycache__/pytester.cpython-310.pyc | Bin 60235 -> 61406 bytes .../pytester_assertions.cpython-310.pyc | Bin 1783 -> 1828 bytes .../__pycache__/python.cpython-310.pyc | Bin 53284 -> 51805 bytes .../__pycache__/python_api.cpython-310.pyc | Bin 32600 -> 26156 bytes .../__pycache__/python_path.cpython-310.pyc | Bin 1040 -> 0 bytes .../__pycache__/raises.cpython-310.pyc | Bin 0 -> 45139 bytes .../__pycache__/recwarn.cpython-310.pyc | Bin 10629 -> 11901 bytes .../__pycache__/reports.cpython-310.pyc | Bin 17537 -> 19483 bytes .../__pycache__/runner.cpython-310.pyc | Bin 15244 -> 16226 bytes .../_pytest/__pycache__/scope.cpython-310.pyc | Bin 3184 -> 3081 bytes .../__pycache__/setuponly.cpython-310.pyc | Bin 2994 -> 3165 bytes .../__pycache__/setupplan.cpython-310.pyc | Bin 1380 -> 1441 bytes .../__pycache__/skipping.cpython-310.pyc | Bin 8361 -> 8854 bytes .../_pytest/__pycache__/stash.cpython-310.pyc | Bin 3827 -> 3970 bytes .../__pycache__/stepwise.cpython-310.pyc | Bin 3857 -> 6294 bytes .../__pycache__/subtests.cpython-310.pyc | Bin 0 -> 11908 bytes .../__pycache__/terminal.cpython-310.pyc | Bin 43420 -> 52402 bytes .../terminalprogress.cpython-310.pyc | Bin 0 -> 850 bytes .../threadexception.cpython-310.pyc | Bin 3296 -> 3672 bytes .../__pycache__/timing.cpython-310.pyc | Bin 566 -> 3821 bytes .../__pycache__/tmpdir.cpython-310.pyc | Bin 9158 -> 9055 bytes .../__pycache__/tracemalloc.cpython-310.pyc | Bin 0 -> 819 bytes .../__pycache__/unittest.cpython-310.pyc | Bin 11527 -> 17663 bytes .../unraisableexception.cpython-310.pyc | Bin 3293 -> 3966 bytes .../__pycache__/warning_types.cpython-310.pyc | Bin 5480 -> 5573 bytes .../__pycache__/warnings.cpython-310.pyc | Bin 4185 -> 4327 bytes .../site-packages/_pytest/_argcomplete.py | 11 +- .../site-packages/_pytest/_code/__init__.py | 12 +- .../__pycache__/__init__.cpython-310.pyc | Bin 613 -> 664 bytes .../_code/__pycache__/code.cpython-310.pyc | Bin 39556 -> 46846 bytes .../_code/__pycache__/source.cpython-310.pyc | Bin 7162 -> 7675 bytes .../site-packages/_pytest/_code/code.py | 666 +- .../site-packages/_pytest/_code/source.py | 74 +- .../site-packages/_pytest/_io/__init__.py | 2 + .../_io/__pycache__/__init__.cpython-310.pyc | Bin 308 -> 359 bytes .../_io/__pycache__/pprint.cpython-310.pyc | Bin 0 -> 13123 bytes .../_io/__pycache__/saferepr.cpython-310.pyc | Bin 5508 -> 4488 bytes .../terminalwriter.cpython-310.pyc | Bin 6615 -> 7847 bytes .../_io/__pycache__/wcwidth.cpython-310.pyc | Bin 1273 -> 1347 bytes .../site-packages/_pytest/_io/pprint.py | 673 ++ .../site-packages/_pytest/_io/saferepr.py | 66 +- .../_pytest/_io/terminalwriter.py | 147 +- .../site-packages/_pytest/_io/wcwidth.py | 4 +- .../_py/__pycache__/__init__.cpython-310.pyc | Bin 181 -> 181 bytes .../_py/__pycache__/error.cpython-310.pyc | Bin 3229 -> 3312 bytes .../_py/__pycache__/path.cpython-310.pyc | Bin 43119 -> 43156 bytes .../site-packages/_pytest/_py/error.py | 22 +- .../site-packages/_pytest/_py/path.py | 92 +- .../site-packages/_pytest/_version.py | 26 +- .../_pytest/assertion/__init__.py | 67 +- .../__pycache__/__init__.cpython-310.pyc | Bin 6661 -> 7723 bytes .../__pycache__/rewrite.cpython-310.pyc | Bin 34923 -> 36104 bytes .../__pycache__/truncate.cpython-310.pyc | Bin 2670 -> 3075 bytes .../__pycache__/util.cpython-310.pyc | Bin 13729 -> 15404 bytes .../_pytest/assertion/rewrite.py | 443 +- .../_pytest/assertion/truncate.py | 78 +- .../site-packages/_pytest/assertion/util.py | 329 +- .../site-packages/_pytest/cacheprovider.py | 224 +- .../site-packages/_pytest/capture.py | 286 +- .../site-packages/_pytest/compat.py | 259 +- .../site-packages/_pytest/config/__init__.py | 1321 ++- .../__pycache__/__init__.cpython-310.pyc | Bin 48303 -> 59674 bytes .../__pycache__/argparsing.cpython-310.pyc | Bin 18268 -> 19580 bytes .../config/__pycache__/compat.cpython-310.pyc | Bin 2252 -> 2856 bytes .../__pycache__/exceptions.cpython-310.pyc | Bin 677 -> 745 bytes .../__pycache__/findpaths.cpython-310.pyc | Bin 6191 -> 9907 bytes .../_pytest/config/argparsing.py | 453 +- .../site-packages/_pytest/config/compat.py | 45 +- .../_pytest/config/exceptions.py | 6 +- .../site-packages/_pytest/config/findpaths.py | 234 +- .../site-packages/_pytest/debugging.py | 128 +- .../site-packages/_pytest/deprecated.py | 91 +- .../site-packages/_pytest/doctest.py | 275 +- .../site-packages/_pytest/faulthandler.py | 55 +- .../site-packages/_pytest/fixtures.py | 2006 ++-- .../site-packages/_pytest/freeze_support.py | 13 +- .../site-packages/_pytest/helpconfig.py | 157 +- .../site-packages/_pytest/hookspec.py | 707 +- .../site-packages/_pytest/junitxml.py | 111 +- .../site-packages/_pytest/legacypath.py | 65 +- .../site-packages/_pytest/logging.py | 244 +- .../python3.10/site-packages/_pytest/main.py | 996 +- .../site-packages/_pytest/mark/__init__.py | 122 +- .../mark/__pycache__/__init__.cpython-310.pyc | Bin 8416 -> 9643 bytes .../__pycache__/expression.cpython-310.pyc | Bin 7371 -> 11449 bytes .../__pycache__/structures.cpython-310.pyc | Bin 19002 -> 21132 bytes .../site-packages/_pytest/mark/expression.py | 231 +- .../site-packages/_pytest/mark/structures.py | 282 +- .../site-packages/_pytest/monkeypatch.py | 84 +- .../python3.10/site-packages/_pytest/nodes.py | 349 +- .../python3.10/site-packages/_pytest/nose.py | 50 - .../site-packages/_pytest/outcomes.py | 249 +- .../site-packages/_pytest/pastebin.py | 29 +- .../site-packages/_pytest/pathlib.py | 549 +- .../site-packages/_pytest/pytester.py | 412 +- .../_pytest/pytester_assertions.py | 21 +- .../site-packages/_pytest/python.py | 1217 ++- .../site-packages/_pytest/python_api.py | 476 +- .../site-packages/_pytest/python_path.py | 24 - .../site-packages/_pytest/raises.py | 1517 +++ .../site-packages/_pytest/recwarn.py | 250 +- .../site-packages/_pytest/reports.py | 268 +- .../site-packages/_pytest/runner.py | 219 +- .../python3.10/site-packages/_pytest/scope.py | 30 +- .../site-packages/_pytest/setuponly.py | 69 +- .../site-packages/_pytest/setupplan.py | 9 +- .../site-packages/_pytest/skipping.py | 106 +- .../python3.10/site-packages/_pytest/stash.py | 12 +- .../site-packages/_pytest/stepwise.py | 135 +- .../site-packages/_pytest/subtests.py | 411 + .../site-packages/_pytest/terminal.py | 708 +- .../site-packages/_pytest/terminalprogress.py | 30 + .../site-packages/_pytest/threadexception.py | 206 +- .../site-packages/_pytest/timing.py | 83 + .../site-packages/_pytest/tmpdir.py | 107 +- .../site-packages/_pytest/tracemalloc.py | 24 + .../site-packages/_pytest/unittest.py | 629 +- .../_pytest/unraisableexception.py | 222 +- .../site-packages/_pytest/warning_types.py | 42 +- .../site-packages/_pytest/warnings.py | 149 +- .../INSTALLER | 0 .../aiohappyeyeballs-2.6.1.dist-info/LICENSE | 279 + .../aiohappyeyeballs-2.6.1.dist-info/METADATA | 123 + .../aiohappyeyeballs-2.6.1.dist-info/RECORD | 16 + .../aiohappyeyeballs-2.6.1.dist-info/WHEEL | 4 + .../aiohappyeyeballs/__init__.py | 14 + .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 486 bytes .../__pycache__/_staggered.cpython-310.pyc | Bin 0 -> 5528 bytes .../__pycache__/impl.cpython-310.pyc | Bin 0 -> 6336 bytes .../__pycache__/types.cpython-310.pyc | Bin 0 -> 458 bytes .../__pycache__/utils.cpython-310.pyc | Bin 0 -> 2545 bytes .../aiohappyeyeballs/_staggered.py | 207 + .../site-packages/aiohappyeyeballs/impl.py | 259 + .../REQUESTED => aiohappyeyeballs/py.typed} | 0 .../site-packages/aiohappyeyeballs/types.py | 17 + .../site-packages/aiohappyeyeballs/utils.py | 97 + .../INSTALLER | 0 .../aiohttp-3.9.1.dist-info/LICENSE.txt | 13 + .../aiohttp-3.9.1.dist-info/METADATA | 243 + .../aiohttp-3.9.1.dist-info/RECORD | 120 + .../REQUESTED | 0 .../aiohttp-3.9.1.dist-info/WHEEL | 6 + .../aiohttp-3.9.1.dist-info/top_level.txt | 1 + .../aiohttp/.hash/_cparser.pxd.hash | 1 + .../aiohttp/.hash/_find_header.pxd.hash | 1 + .../aiohttp/.hash/_helpers.pyi.hash | 1 + .../aiohttp/.hash/_helpers.pyx.hash | 1 + .../aiohttp/.hash/_http_parser.pyx.hash | 1 + .../aiohttp/.hash/_http_writer.pyx.hash | 1 + .../aiohttp/.hash/_websocket.pyx.hash | 1 + .../site-packages/aiohttp/.hash/hdrs.py.hash | 1 + .../site-packages/aiohttp/__init__.py | 240 + .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 4158 bytes .../aiohttp/__pycache__/abc.cpython-310.pyc | Bin 0 -> 8575 bytes .../__pycache__/base_protocol.cpython-310.pyc | Bin 0 -> 2819 bytes .../__pycache__/client.cpython-310.pyc | Bin 0 -> 30921 bytes .../client_exceptions.cpython-310.pyc | Bin 0 -> 11168 bytes .../__pycache__/client_proto.cpython-310.pyc | Bin 0 -> 6507 bytes .../__pycache__/client_reqrep.cpython-310.pyc | Bin 0 -> 30771 bytes .../__pycache__/client_ws.cpython-310.pyc | Bin 0 -> 9194 bytes .../compression_utils.cpython-310.pyc | Bin 0 -> 5222 bytes .../__pycache__/connector.cpython-310.pyc | Bin 0 -> 36467 bytes .../__pycache__/cookiejar.cpython-310.pyc | Bin 0 -> 10806 bytes .../__pycache__/formdata.cpython-310.pyc | Bin 0 -> 4550 bytes .../aiohttp/__pycache__/hdrs.cpython-310.pyc | Bin 0 -> 5198 bytes .../__pycache__/helpers.cpython-310.pyc | Bin 0 -> 29567 bytes .../aiohttp/__pycache__/http.cpython-310.pyc | Bin 0 -> 1467 bytes .../http_exceptions.cpython-310.pyc | Bin 0 -> 4433 bytes .../__pycache__/http_parser.cpython-310.pyc | Bin 0 -> 19093 bytes .../http_websocket.cpython-310.pyc | Bin 0 -> 15619 bytes .../__pycache__/http_writer.cpython-310.pyc | Bin 0 -> 5673 bytes .../aiohttp/__pycache__/locks.cpython-310.pyc | Bin 0 -> 1650 bytes .../aiohttp/__pycache__/log.cpython-310.pyc | Bin 0 -> 457 bytes .../__pycache__/multipart.cpython-310.pyc | Bin 0 -> 26545 bytes .../__pycache__/payload.cpython-310.pyc | Bin 0 -> 13803 bytes .../payload_streamer.cpython-310.pyc | Bin 0 -> 3266 bytes .../__pycache__/pytest_plugin.cpython-310.pyc | Bin 0 -> 9721 bytes .../__pycache__/resolver.cpython-310.pyc | Bin 0 -> 4034 bytes .../__pycache__/streams.cpython-310.pyc | Bin 0 -> 18758 bytes .../__pycache__/tcp_helpers.cpython-310.pyc | Bin 0 -> 1146 bytes .../__pycache__/test_utils.cpython-310.pyc | Bin 0 -> 20979 bytes .../__pycache__/tracing.cpython-310.pyc | Bin 0 -> 14312 bytes .../__pycache__/typedefs.cpython-310.pyc | Bin 0 -> 1383 bytes .../aiohttp/__pycache__/web.cpython-310.pyc | Bin 0 -> 11432 bytes .../__pycache__/web_app.cpython-310.pyc | Bin 0 -> 16526 bytes .../web_exceptions.cpython-310.pyc | Bin 0 -> 11652 bytes .../web_fileresponse.cpython-310.pyc | Bin 0 -> 5998 bytes .../__pycache__/web_log.cpython-310.pyc | Bin 0 -> 7376 bytes .../web_middlewares.cpython-310.pyc | Bin 0 -> 3857 bytes .../__pycache__/web_protocol.cpython-310.pyc | Bin 0 -> 17065 bytes .../__pycache__/web_request.cpython-310.pyc | Bin 0 -> 24345 bytes .../__pycache__/web_response.cpython-310.pyc | Bin 0 -> 21113 bytes .../__pycache__/web_routedef.cpython-310.pyc | Bin 0 -> 7660 bytes .../__pycache__/web_runner.cpython-310.pyc | Bin 0 -> 12062 bytes .../__pycache__/web_server.cpython-310.pyc | Bin 0 -> 3335 bytes .../web_urldispatcher.cpython-310.pyc | Bin 0 -> 41642 bytes .../__pycache__/web_ws.cpython-310.pyc | Bin 0 -> 13998 bytes .../__pycache__/worker.cpython-310.pyc | Bin 0 -> 6531 bytes .../site-packages/aiohttp/_cparser.pxd | 158 + .../site-packages/aiohttp/_find_header.pxd | 2 + .../site-packages/aiohttp/_headers.pxi | 83 + .../_helpers.cpython-310-x86_64-linux-gnu.so | Bin 0 -> 508784 bytes .../site-packages/aiohttp/_helpers.pyi | 6 + .../site-packages/aiohttp/_helpers.pyx | 35 + ...ttp_parser.cpython-310-x86_64-linux-gnu.so | Bin 0 -> 2587480 bytes .../site-packages/aiohttp/_http_parser.pyx | 836 ++ ...ttp_writer.cpython-310-x86_64-linux-gnu.so | Bin 0 -> 459368 bytes .../site-packages/aiohttp/_http_writer.pyx | 163 + ..._websocket.cpython-310-x86_64-linux-gnu.so | Bin 0 -> 234032 bytes .../site-packages/aiohttp/_websocket.pyx | 56 + .../python3.10/site-packages/aiohttp/abc.py | 209 + .../site-packages/aiohttp/base_protocol.py | 90 + .../site-packages/aiohttp/client.py | 1356 +++ .../aiohttp/client_exceptions.py | 346 + .../site-packages/aiohttp/client_proto.py | 264 + .../site-packages/aiohttp/client_reqrep.py | 1196 +++ .../site-packages/aiohttp/client_ws.py | 315 + .../aiohttp/compression_utils.py | 157 + .../site-packages/aiohttp/connector.py | 1489 +++ .../site-packages/aiohttp/cookiejar.py | 419 + .../site-packages/aiohttp/formdata.py | 172 + .../python3.10/site-packages/aiohttp/hdrs.py | 108 + .../site-packages/aiohttp/helpers.py | 999 ++ .../python3.10/site-packages/aiohttp/http.py | 72 + .../site-packages/aiohttp/http_exceptions.py | 106 + .../site-packages/aiohttp/http_parser.py | 1009 ++ .../site-packages/aiohttp/http_websocket.py | 740 ++ .../site-packages/aiohttp/http_writer.py | 198 + .../python3.10/site-packages/aiohttp/locks.py | 41 + .../python3.10/site-packages/aiohttp/log.py | 8 + .../site-packages/aiohttp/multipart.py | 969 ++ .../site-packages/aiohttp/payload.py | 463 + .../site-packages/aiohttp/payload_streamer.py | 75 + .../python3.10/site-packages/aiohttp/py.typed | 1 + .../site-packages/aiohttp/pytest_plugin.py | 381 + .../site-packages/aiohttp/resolver.py | 160 + .../site-packages/aiohttp/streams.py | 666 ++ .../site-packages/aiohttp/tcp_helpers.py | 37 + .../site-packages/aiohttp/test_utils.py | 675 ++ .../site-packages/aiohttp/tracing.py | 471 + .../site-packages/aiohttp/typedefs.py | 54 + .../python3.10/site-packages/aiohttp/web.py | 616 ++ .../site-packages/aiohttp/web_app.py | 596 ++ .../site-packages/aiohttp/web_exceptions.py | 452 + .../site-packages/aiohttp/web_fileresponse.py | 285 + .../site-packages/aiohttp/web_log.py | 213 + .../site-packages/aiohttp/web_middlewares.py | 116 + .../site-packages/aiohttp/web_protocol.py | 698 ++ .../site-packages/aiohttp/web_request.py | 898 ++ .../site-packages/aiohttp/web_response.py | 817 ++ .../site-packages/aiohttp/web_routedef.py | 216 + .../site-packages/aiohttp/web_runner.py | 406 + .../site-packages/aiohttp/web_server.py | 77 + .../aiohttp/web_urldispatcher.py | 1222 +++ .../site-packages/aiohttp/web_ws.py | 515 + .../site-packages/aiohttp/worker.py | 247 + .../INSTALLER | 0 .../aiosignal-1.4.0.dist-info/METADATA | 112 + .../aiosignal-1.4.0.dist-info/RECORD | 9 + .../WHEEL | 2 +- .../licenses/LICENSE | 201 + .../aiosignal-1.4.0.dist-info/top_level.txt | 1 + .../site-packages/aiosignal/__init__.py | 59 + .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 2065 bytes .../site-packages/aiosignal/py.typed | 0 .../async_timeout-4.0.3.dist-info/INSTALLER | 1 + .../LICENSE | 0 .../METADATA | 60 +- .../async_timeout-4.0.3.dist-info/RECORD | 10 + .../WHEEL | 2 +- .../top_level.txt | 0 .../zip-safe | 0 .../async_timeout-5.0.1.dist-info/RECORD | 10 - .../site-packages/async_timeout/__init__.py | 353 +- .../__pycache__/__init__.cpython-310.pyc | Bin 7629 -> 6570 bytes .../python3.10/site-packages/attr/__init__.py | 104 + .../site-packages/attr/__init__.pyi | 389 + .../attr/__pycache__/__init__.cpython-310.pyc | Bin 0 -> 2246 bytes .../attr/__pycache__/_cmp.cpython-310.pyc | Bin 0 -> 4023 bytes .../attr/__pycache__/_compat.cpython-310.pyc | Bin 0 -> 2510 bytes .../attr/__pycache__/_config.cpython-310.pyc | Bin 0 -> 1005 bytes .../attr/__pycache__/_funcs.cpython-310.pyc | Bin 0 -> 10863 bytes .../attr/__pycache__/_make.cpython-310.pyc | Bin 0 -> 74906 bytes .../__pycache__/_next_gen.cpython-310.pyc | Bin 0 -> 25532 bytes .../__pycache__/_version_info.cpython-310.pyc | Bin 0 -> 2487 bytes .../__pycache__/converters.cpython-310.pyc | Bin 0 -> 3735 bytes .../__pycache__/exceptions.cpython-310.pyc | Bin 0 -> 3163 bytes .../attr/__pycache__/filters.cpython-310.pyc | Bin 0 -> 2251 bytes .../attr/__pycache__/setters.cpython-310.pyc | Bin 0 -> 1597 bytes .../__pycache__/validators.cpython-310.pyc | Bin 0 -> 21242 bytes .../lib/python3.10/site-packages/attr/_cmp.py | 160 + .../python3.10/site-packages/attr/_cmp.pyi | 13 + .../python3.10/site-packages/attr/_compat.py | 99 + .../python3.10/site-packages/attr/_config.py | 31 + .../python3.10/site-packages/attr/_funcs.py | 497 + .../python3.10/site-packages/attr/_make.py | 3362 +++++++ .../site-packages/attr/_next_gen.py | 674 ++ .../site-packages/attr/_typing_compat.pyi | 15 + .../site-packages/attr/_version_info.py | 89 + .../site-packages/attr/_version_info.pyi | 9 + .../site-packages/attr/converters.py | 162 + .../site-packages/attr/converters.pyi | 19 + .../site-packages/attr/exceptions.py | 95 + .../site-packages/attr/exceptions.pyi | 17 + .../python3.10/site-packages/attr/filters.py | 72 + .../python3.10/site-packages/attr/filters.pyi | 6 + .../python3.10/site-packages/attr/py.typed | 0 .../python3.10/site-packages/attr/setters.py | 79 + .../python3.10/site-packages/attr/setters.pyi | 20 + .../site-packages/attr/validators.py | 748 ++ .../site-packages/attr/validators.pyi | 140 + .../attrs-25.4.0.dist-info/INSTALLER | 1 + .../attrs-25.4.0.dist-info/METADATA | 235 + .../attrs-25.4.0.dist-info/RECORD | 55 + .../attrs-25.4.0.dist-info/WHEEL | 4 + .../attrs-25.4.0.dist-info/licenses/LICENSE | 21 + .../site-packages/attrs/__init__.py | 72 + .../site-packages/attrs/__init__.pyi | 314 + .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 1143 bytes .../__pycache__/converters.cpython-310.pyc | Bin 0 -> 209 bytes .../__pycache__/exceptions.cpython-310.pyc | Bin 0 -> 209 bytes .../attrs/__pycache__/filters.cpython-310.pyc | Bin 0 -> 203 bytes .../attrs/__pycache__/setters.cpython-310.pyc | Bin 0 -> 203 bytes .../__pycache__/validators.cpython-310.pyc | Bin 0 -> 209 bytes .../site-packages/attrs/converters.py | 3 + .../site-packages/attrs/exceptions.py | 3 + .../python3.10/site-packages/attrs/filters.py | 3 + .../python3.10/site-packages/attrs/py.typed | 0 .../python3.10/site-packages/attrs/setters.py | 3 + .../site-packages/attrs/validators.py | 3 + .../backports/asyncio/runner/__init__.py | 37 + .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 1717 bytes .../__pycache__/_int_to_enum.cpython-310.pyc | Bin 0 -> 860 bytes .../runner/__pycache__/_patch.cpython-310.pyc | Bin 0 -> 664 bytes .../runner/__pycache__/runner.cpython-310.pyc | Bin 0 -> 7258 bytes .../runner/__pycache__/tasks.cpython-310.pyc | Bin 0 -> 3147 bytes .../backports/asyncio/runner/_int_to_enum.py | 20 + .../backports/asyncio/runner/_patch.py | 18 + .../backports/asyncio/runner/py.typed | 0 .../backports/asyncio/runner/runner.py | 275 + .../backports/asyncio/runner/runner.pyi | 26 + .../backports/asyncio/runner/tasks.py | 94 + .../INSTALLER | 1 + .../METADATA | 162 + .../RECORD | 17 + .../WHEEL | 4 + .../licenses/LICENSE.md | 10 + .../certifi-2025.11.12.dist-info/INSTALLER | 1 + .../certifi-2025.11.12.dist-info/METADATA | 78 + .../certifi-2025.11.12.dist-info/RECORD | 14 + .../certifi-2025.11.12.dist-info/WHEEL | 5 + .../licenses/LICENSE | 20 + .../top_level.txt | 1 + .../site-packages/certifi/__init__.py | 4 + .../site-packages/certifi/__main__.py | 12 + .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 290 bytes .../__pycache__/__main__.cpython-310.pyc | Bin 0 -> 428 bytes .../certifi/__pycache__/core.cpython-310.pyc | Bin 0 -> 1403 bytes .../site-packages/certifi/cacert.pem | 4678 +++++++++ .../python3.10/site-packages/certifi/core.py | 83 + .../python3.10/site-packages/certifi/py.typed | 0 .../curl_cffi-0.14.0.dist-info/INSTALLER | 1 + .../curl_cffi-0.14.0.dist-info/METADATA | 343 + .../curl_cffi-0.14.0.dist-info/RECORD | 47 + .../curl_cffi-0.14.0.dist-info/REQUESTED | 0 .../curl_cffi-0.14.0.dist-info/WHEEL | 6 + .../entry_points.txt | 2 + .../licenses}/LICENSE | 3 +- .../top_level.txt | 0 .../curl_cffi-0.5.10.dist-info/METADATA | 226 - .../curl_cffi-0.5.10.dist-info/RECORD | 50 - .../curl_cffi-0.5.10.dist-info/WHEEL | 6 - ...bcurl-impersonate-chrome-ad79e5fb.so.4.8.0 | Bin 19939225 -> 0 bytes .../site-packages/curl_cffi/__init__.py | 87 +- .../__pycache__/__init__.cpython-310.pyc | Bin 618 -> 1526 bytes .../__pycache__/__version__.cpython-310.pyc | Bin 405 -> 408 bytes .../_asyncio_selector.cpython-310.pyc | Bin 0 -> 9085 bytes .../curl_cffi/__pycache__/aio.cpython-310.pyc | Bin 5974 -> 9196 bytes .../__pycache__/build.cpython-310.pyc | Bin 1064 -> 0 bytes .../curl_cffi/__pycache__/cli.cpython-310.pyc | Bin 0 -> 1035 bytes .../__pycache__/const.cpython-310.pyc | Bin 14581 -> 17289 bytes .../__pycache__/curl.cpython-310.pyc | Bin 10285 -> 20158 bytes .../__pycache__/utils.cpython-310.pyc | Bin 0 -> 719 bytes .../site-packages/curl_cffi/__version__.py | 11 +- .../curl_cffi/_asyncio_selector.py | 344 + .../site-packages/curl_cffi/_wrapper.abi3.so | Bin 136505 -> 23747304 bytes .../python3.10/site-packages/curl_cffi/aio.py | 275 +- .../site-packages/curl_cffi/build.py | 44 - .../site-packages/curl_cffi/cacert.pem | 3451 ------- .../python3.10/site-packages/curl_cffi/cli.py | 32 + .../site-packages/curl_cffi/const.py | 136 +- .../site-packages/curl_cffi/curl.py | 520 +- .../site-packages/curl_cffi/ffi/cdef.c | 48 - .../site-packages/curl_cffi/ffi/shim.c | 16 - .../site-packages/curl_cffi/ffi/shim.h | 7 - .../curl_cffi/include/curl/Makefile.am | 41 - .../curl_cffi/include/curl/Makefile.in | 716 -- .../curl_cffi/include/curl/curl.h | 3160 ------ .../curl_cffi/include/curl/curlver.h | 79 - .../curl_cffi/include/curl/easy.h | 135 - .../curl_cffi/include/curl/header.h | 66 - .../curl_cffi/include/curl/mprintf.h | 52 - .../curl_cffi/include/curl/multi.h | 460 - .../curl_cffi/include/curl/options.h | 70 - .../curl_cffi/include/curl/stdcheaders.h | 35 - .../curl_cffi/include/curl/system.h | 490 - .../curl_cffi/include/curl/typecheck-gcc.h | 710 -- .../curl_cffi/include/curl/urlapi.h | 147 - .../site-packages/curl_cffi/py.typed | 1 + .../curl_cffi/requests/__init__.py | 190 +- .../__pycache__/__init__.cpython-310.pyc | Bin 4125 -> 6175 bytes .../__pycache__/cookies.cpython-310.pyc | Bin 9670 -> 10341 bytes .../__pycache__/errors.cpython-310.pyc | Bin 844 -> 397 bytes .../__pycache__/exceptions.cpython-310.pyc | Bin 0 -> 7542 bytes .../__pycache__/headers.cpython-310.pyc | Bin 10543 -> 10808 bytes .../__pycache__/impersonate.cpython-310.pyc | Bin 0 -> 9199 bytes .../__pycache__/models.cpython-310.pyc | Bin 5542 -> 9702 bytes .../__pycache__/session.cpython-310.pyc | Bin 24017 -> 37434 bytes .../__pycache__/utils.cpython-310.pyc | Bin 0 -> 17127 bytes .../__pycache__/websockets.cpython-310.pyc | Bin 0 -> 42892 bytes .../curl_cffi/requests/cookies.py | 124 +- .../curl_cffi/requests/errors.py | 16 +- .../curl_cffi/requests/exceptions.py | 227 + .../curl_cffi/requests/headers.py | 161 +- .../curl_cffi/requests/impersonate.py | 441 + .../curl_cffi/requests/models.py | 232 +- .../curl_cffi/requests/session.py | 1317 ++- .../site-packages/curl_cffi/requests/utils.py | 699 ++ .../curl_cffi/requests/websockets.py | 1432 +++ .../site-packages/curl_cffi/utils.py | 16 + .../frozenlist-1.8.0.dist-info/INSTALLER | 1 + .../frozenlist-1.8.0.dist-info/METADATA | 672 ++ .../frozenlist-1.8.0.dist-info/RECORD | 12 + .../frozenlist-1.8.0.dist-info/WHEEL | 7 + .../licenses/LICENSE | 201 + .../frozenlist-1.8.0.dist-info/top_level.txt | 1 + .../site-packages/frozenlist/__init__.py | 86 + .../site-packages/frozenlist/__init__.pyi | 47 + .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 2988 bytes ...frozenlist.cpython-310-x86_64-linux-gnu.so | Bin 0 -> 699088 bytes .../site-packages/frozenlist/_frozenlist.pyx | 148 + .../site-packages/frozenlist/py.typed | 1 + .../idna-3.11.dist-info/INSTALLER | 1 + .../idna-3.11.dist-info/METADATA | 209 + .../site-packages/idna-3.11.dist-info/RECORD | 22 + .../site-packages/idna-3.11.dist-info/WHEEL | 4 + .../idna-3.11.dist-info/licenses/LICENSE.md | 31 + .../python3.10/site-packages/idna/__init__.py | 45 + .../idna/__pycache__/__init__.cpython-310.pyc | Bin 0 -> 838 bytes .../idna/__pycache__/codec.cpython-310.pyc | Bin 0 -> 3259 bytes .../idna/__pycache__/compat.cpython-310.pyc | Bin 0 -> 741 bytes .../idna/__pycache__/core.cpython-310.pyc | Bin 0 -> 9661 bytes .../idna/__pycache__/idnadata.cpython-310.pyc | Bin 0 -> 198637 bytes .../__pycache__/intranges.cpython-310.pyc | Bin 0 -> 1970 bytes .../__pycache__/package_data.cpython-310.pyc | Bin 0 -> 198 bytes .../__pycache__/uts46data.cpython-310.pyc | Bin 0 -> 155206 bytes .../python3.10/site-packages/idna/codec.py | 122 + .../python3.10/site-packages/idna/compat.py | 15 + .../lib/python3.10/site-packages/idna/core.py | 437 + .../python3.10/site-packages/idna/idnadata.py | 4309 ++++++++ .../site-packages/idna/intranges.py | 57 + .../site-packages/idna/package_data.py | 1 + .../python3.10/site-packages/idna/py.typed | 0 .../site-packages/idna/uts46data.py | 8841 +++++++++++++++++ .../multidict-6.7.0.dist-info/INSTALLER | 1 + .../multidict-6.7.0.dist-info/METADATA | 149 + .../multidict-6.7.0.dist-info/RECORD | 16 + .../multidict-6.7.0.dist-info/WHEEL | 7 + .../licenses/LICENSE | 13 + .../multidict-6.7.0.dist-info/top_level.txt | 1 + .../site-packages/multidict/__init__.py | 60 + .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 1125 bytes .../__pycache__/_abc.cpython-310.pyc | Bin 0 -> 4126 bytes .../__pycache__/_compat.cpython-310.pyc | Bin 0 -> 471 bytes .../__pycache__/_multidict_py.cpython-310.pyc | Bin 0 -> 37558 bytes .../site-packages/multidict/_abc.py | 73 + .../site-packages/multidict/_compat.py | 15 + ..._multidict.cpython-310-x86_64-linux-gnu.so | Bin 0 -> 788880 bytes .../site-packages/multidict/_multidict_py.py | 1242 +++ .../site-packages/multidict/py.typed | 1 + .../propcache-0.4.1.dist-info/INSTALLER | 1 + .../propcache-0.4.1.dist-info/METADATA | 443 + .../propcache-0.4.1.dist-info/RECORD | 18 + .../propcache-0.4.1.dist-info/WHEEL | 7 + .../licenses/LICENSE | 202 + .../propcache-0.4.1.dist-info/licenses/NOTICE | 13 + .../propcache-0.4.1.dist-info/top_level.txt | 1 + .../site-packages/propcache/__init__.py | 32 + .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 1061 bytes .../__pycache__/_helpers.cpython-310.pyc | Bin 0 -> 806 bytes .../__pycache__/_helpers_py.cpython-310.pyc | Bin 0 -> 2460 bytes .../propcache/__pycache__/api.cpython-310.pyc | Bin 0 -> 329 bytes .../site-packages/propcache/_helpers.py | 39 + ..._helpers_c.cpython-310-x86_64-linux-gnu.so | Bin 0 -> 642864 bytes .../site-packages/propcache/_helpers_c.pyx | 103 + .../site-packages/propcache/_helpers_py.py | 62 + .../python3.10/site-packages/propcache/api.py | 8 + .../site-packages/propcache/py.typed | 1 + venv/lib/python3.10/site-packages/py.py | 5 + .../pygments-2.19.2.dist-info/INSTALLER | 1 + .../pygments-2.19.2.dist-info/METADATA | 58 + .../pygments-2.19.2.dist-info/RECORD | 684 ++ .../pygments-2.19.2.dist-info/WHEEL | 4 + .../entry_points.txt | 2 + .../licenses/AUTHORS | 291 + .../licenses/LICENSE | 25 + .../site-packages/pygments/__init__.py | 82 + .../site-packages/pygments/__main__.py | 17 + .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 2897 bytes .../__pycache__/__main__.cpython-310.pyc | Bin 0 -> 574 bytes .../__pycache__/cmdline.cpython-310.pyc | Bin 0 -> 15418 bytes .../__pycache__/console.cpython-310.pyc | Bin 0 -> 1876 bytes .../__pycache__/filter.cpython-310.pyc | Bin 0 -> 2636 bytes .../__pycache__/formatter.cpython-310.pyc | Bin 0 -> 4063 bytes .../__pycache__/lexer.cpython-310.pyc | Bin 0 -> 26561 bytes .../__pycache__/modeline.cpython-310.pyc | Bin 0 -> 1179 bytes .../__pycache__/plugin.cpython-310.pyc | Bin 0 -> 1935 bytes .../__pycache__/regexopt.cpython-310.pyc | Bin 0 -> 2942 bytes .../__pycache__/scanner.cpython-310.pyc | Bin 0 -> 3544 bytes .../__pycache__/sphinxext.cpython-310.pyc | Bin 0 -> 7680 bytes .../__pycache__/style.cpython-310.pyc | Bin 0 -> 4588 bytes .../__pycache__/token.cpython-310.pyc | Bin 0 -> 4682 bytes .../__pycache__/unistring.cpython-310.pyc | Bin 0 -> 31216 bytes .../pygments/__pycache__/util.cpython-310.pyc | Bin 0 -> 10048 bytes .../site-packages/pygments/cmdline.py | 668 ++ .../site-packages/pygments/console.py | 70 + .../site-packages/pygments/filter.py | 70 + .../pygments/filters/__init__.py | 940 ++ .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 29560 bytes .../site-packages/pygments/formatter.py | 129 + .../pygments/formatters/__init__.py | 157 + .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 4969 bytes .../__pycache__/_mapping.cpython-310.pyc | Bin 0 -> 3986 bytes .../__pycache__/bbcode.cpython-310.pyc | Bin 0 -> 3057 bytes .../__pycache__/groff.cpython-310.pyc | Bin 0 -> 4384 bytes .../__pycache__/html.cpython-310.pyc | Bin 0 -> 29483 bytes .../__pycache__/img.cpython-310.pyc | Bin 0 -> 18397 bytes .../__pycache__/irc.cpython-310.pyc | Bin 0 -> 4048 bytes .../__pycache__/latex.cpython-310.pyc | Bin 0 -> 13830 bytes .../__pycache__/other.cpython-310.pyc | Bin 0 -> 4748 bytes .../__pycache__/pangomarkup.cpython-310.pyc | Bin 0 -> 2088 bytes .../__pycache__/rtf.cpython-310.pyc | Bin 0 -> 8810 bytes .../__pycache__/svg.cpython-310.pyc | Bin 0 -> 6269 bytes .../__pycache__/terminal.cpython-310.pyc | Bin 0 -> 3937 bytes .../__pycache__/terminal256.cpython-310.pyc | Bin 0 -> 9199 bytes .../pygments/formatters/_mapping.py | 23 + .../pygments/formatters/bbcode.py | 108 + .../pygments/formatters/groff.py | 170 + .../site-packages/pygments/formatters/html.py | 995 ++ .../site-packages/pygments/formatters/img.py | 686 ++ .../site-packages/pygments/formatters/irc.py | 154 + .../pygments/formatters/latex.py | 518 + .../pygments/formatters/other.py | 160 + .../pygments/formatters/pangomarkup.py | 83 + .../site-packages/pygments/formatters/rtf.py | 349 + .../site-packages/pygments/formatters/svg.py | 185 + .../pygments/formatters/terminal.py | 127 + .../pygments/formatters/terminal256.py | 338 + .../site-packages/pygments/lexer.py | 961 ++ .../site-packages/pygments/lexers/__init__.py | 362 + .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 9999 bytes .../__pycache__/_ada_builtins.cpython-310.pyc | Bin 0 -> 1216 bytes .../__pycache__/_asy_builtins.cpython-310.pyc | Bin 0 -> 17623 bytes .../__pycache__/_cl_builtins.cpython-310.pyc | Bin 0 -> 11619 bytes .../_cocoa_builtins.cpython-310.pyc | Bin 0 -> 96419 bytes .../_csound_builtins.cpython-310.pyc | Bin 0 -> 16273 bytes .../__pycache__/_css_builtins.cpython-310.pyc | Bin 0 -> 9371 bytes .../_googlesql_builtins.cpython-310.pyc | Bin 0 -> 10798 bytes .../_julia_builtins.cpython-310.pyc | Bin 0 -> 8204 bytes .../_lasso_builtins.cpython-310.pyc | Bin 0 -> 76832 bytes .../_lilypond_builtins.cpython-310.pyc | Bin 0 -> 88326 bytes .../__pycache__/_lua_builtins.cpython-310.pyc | Bin 0 -> 6497 bytes .../_luau_builtins.cpython-310.pyc | Bin 0 -> 1031 bytes .../__pycache__/_mapping.cpython-310.pyc | Bin 0 -> 63876 bytes .../__pycache__/_mql_builtins.cpython-310.pyc | Bin 0 -> 17984 bytes .../_mysql_builtins.cpython-310.pyc | Bin 0 -> 17152 bytes .../_openedge_builtins.cpython-310.pyc | Bin 0 -> 34076 bytes .../__pycache__/_php_builtins.cpython-310.pyc | Bin 0 -> 68028 bytes .../_postgres_builtins.cpython-310.pyc | Bin 0 -> 9048 bytes .../_qlik_builtins.cpython-310.pyc | Bin 0 -> 6344 bytes .../_scheme_builtins.cpython-310.pyc | Bin 0 -> 23152 bytes .../_scilab_builtins.cpython-310.pyc | Bin 0 -> 34215 bytes .../_sourcemod_builtins.cpython-310.pyc | Bin 0 -> 20641 bytes .../__pycache__/_sql_builtins.cpython-310.pyc | Bin 0 -> 5547 bytes .../_stan_builtins.cpython-310.pyc | Bin 0 -> 9923 bytes .../_stata_builtins.cpython-310.pyc | Bin 0 -> 21212 bytes .../_tsql_builtins.cpython-310.pyc | Bin 0 -> 8776 bytes .../__pycache__/_usd_builtins.cpython-310.pyc | Bin 0 -> 1360 bytes .../_vbscript_builtins.cpython-310.pyc | Bin 0 -> 2882 bytes .../__pycache__/_vim_builtins.cpython-310.pyc | Bin 0 -> 30665 bytes .../__pycache__/actionscript.cpython-310.pyc | Bin 0 -> 8911 bytes .../lexers/__pycache__/ada.cpython-310.pyc | Bin 0 -> 3450 bytes .../lexers/__pycache__/agile.cpython-310.pyc | Bin 0 -> 1271 bytes .../__pycache__/algebra.cpython-310.pyc | Bin 0 -> 8369 bytes .../__pycache__/ambient.cpython-310.pyc | Bin 0 -> 2309 bytes .../lexers/__pycache__/amdgpu.cpython-310.pyc | Bin 0 -> 1757 bytes .../lexers/__pycache__/ampl.cpython-310.pyc | Bin 0 -> 3253 bytes .../__pycache__/apdlexer.cpython-310.pyc | Bin 0 -> 18326 bytes .../lexers/__pycache__/apl.cpython-310.pyc | Bin 0 -> 1986 bytes .../__pycache__/archetype.cpython-310.pyc | Bin 0 -> 6707 bytes .../lexers/__pycache__/arrow.cpython-310.pyc | Bin 0 -> 2369 bytes .../lexers/__pycache__/arturo.cpython-310.pyc | Bin 0 -> 6702 bytes .../lexers/__pycache__/asc.cpython-310.pyc | Bin 0 -> 1757 bytes .../lexers/__pycache__/asm.cpython-310.pyc | Bin 0 -> 26202 bytes .../lexers/__pycache__/asn1.cpython-310.pyc | Bin 0 -> 3406 bytes .../__pycache__/automation.cpython-310.pyc | Bin 0 -> 16176 bytes .../lexers/__pycache__/bare.cpython-310.pyc | Bin 0 -> 2124 bytes .../lexers/__pycache__/basic.cpython-310.pyc | Bin 0 -> 17328 bytes .../lexers/__pycache__/bdd.cpython-310.pyc | Bin 0 -> 1833 bytes .../lexers/__pycache__/berry.cpython-310.pyc | Bin 0 -> 2538 bytes .../lexers/__pycache__/bibtex.cpython-310.pyc | Bin 0 -> 3632 bytes .../__pycache__/blueprint.cpython-310.pyc | Bin 0 -> 3608 bytes .../lexers/__pycache__/boa.cpython-310.pyc | Bin 0 -> 2987 bytes .../lexers/__pycache__/bqn.cpython-310.pyc | Bin 0 -> 1952 bytes .../__pycache__/business.cpython-310.pyc | Bin 0 -> 18865 bytes .../lexers/__pycache__/c_cpp.cpython-310.pyc | Bin 0 -> 11574 bytes .../lexers/__pycache__/c_like.cpython-310.pyc | Bin 0 -> 21902 bytes .../__pycache__/capnproto.cpython-310.pyc | Bin 0 -> 1744 bytes .../lexers/__pycache__/carbon.cpython-310.pyc | Bin 0 -> 2759 bytes .../lexers/__pycache__/cddl.cpython-310.pyc | Bin 0 -> 3072 bytes .../lexers/__pycache__/chapel.cpython-310.pyc | Bin 0 -> 3310 bytes .../lexers/__pycache__/clean.cpython-310.pyc | Bin 0 -> 3969 bytes .../lexers/__pycache__/codeql.cpython-310.pyc | Bin 0 -> 2080 bytes .../lexers/__pycache__/comal.cpython-310.pyc | Bin 0 -> 2555 bytes .../__pycache__/compiled.cpython-310.pyc | Bin 0 -> 1944 bytes .../__pycache__/configs.cpython-310.pyc | Bin 0 -> 31783 bytes .../__pycache__/console.cpython-310.pyc | Bin 0 -> 3558 bytes .../lexers/__pycache__/cplint.cpython-310.pyc | Bin 0 -> 1546 bytes .../__pycache__/crystal.cpython-310.pyc | Bin 0 -> 8537 bytes .../lexers/__pycache__/csound.cpython-310.pyc | Bin 0 -> 8949 bytes .../lexers/__pycache__/css.cpython-310.pyc | Bin 0 -> 15694 bytes .../lexers/__pycache__/d.cpython-310.pyc | Bin 0 -> 6196 bytes .../lexers/__pycache__/dalvik.cpython-310.pyc | Bin 0 -> 3444 bytes .../lexers/__pycache__/data.cpython-310.pyc | Bin 0 -> 13163 bytes .../lexers/__pycache__/dax.cpython-310.pyc | Bin 0 -> 5653 bytes .../__pycache__/devicetree.cpython-310.pyc | Bin 0 -> 2701 bytes .../lexers/__pycache__/diff.cpython-310.pyc | Bin 0 -> 4015 bytes .../lexers/__pycache__/dns.cpython-310.pyc | Bin 0 -> 2585 bytes .../lexers/__pycache__/dotnet.cpython-310.pyc | Bin 0 -> 25543 bytes .../lexers/__pycache__/dsls.cpython-310.pyc | Bin 0 -> 24148 bytes .../lexers/__pycache__/dylan.cpython-310.pyc | Bin 0 -> 7181 bytes .../lexers/__pycache__/ecl.cpython-310.pyc | Bin 0 -> 4749 bytes .../lexers/__pycache__/eiffel.cpython-310.pyc | Bin 0 -> 2359 bytes .../lexers/__pycache__/elm.cpython-310.pyc | Bin 0 -> 2254 bytes .../lexers/__pycache__/elpi.cpython-310.pyc | Bin 0 -> 4297 bytes .../lexers/__pycache__/email.cpython-310.pyc | Bin 0 -> 4407 bytes .../lexers/__pycache__/erlang.cpython-310.pyc | Bin 0 -> 12893 bytes .../__pycache__/esoteric.cpython-310.pyc | Bin 0 -> 7800 bytes .../lexers/__pycache__/ezhil.cpython-310.pyc | Bin 0 -> 3270 bytes .../lexers/__pycache__/factor.cpython-310.pyc | Bin 0 -> 12986 bytes .../lexers/__pycache__/fantom.cpython-310.pyc | Bin 0 -> 5000 bytes .../lexers/__pycache__/felix.cpython-310.pyc | Bin 0 -> 5747 bytes .../lexers/__pycache__/fift.cpython-310.pyc | Bin 0 -> 1503 bytes .../__pycache__/floscript.cpython-310.pyc | Bin 0 -> 2367 bytes .../lexers/__pycache__/forth.cpython-310.pyc | Bin 0 -> 4739 bytes .../__pycache__/fortran.cpython-310.pyc | Bin 0 -> 7404 bytes .../lexers/__pycache__/foxpro.cpython-310.pyc | Bin 0 -> 20170 bytes .../__pycache__/freefem.cpython-310.pyc | Bin 0 -> 12035 bytes .../lexers/__pycache__/func.cpython-310.pyc | Bin 0 -> 2533 bytes .../__pycache__/functional.cpython-310.pyc | Bin 0 -> 1007 bytes .../__pycache__/futhark.cpython-310.pyc | Bin 0 -> 2802 bytes .../__pycache__/gcodelexer.cpython-310.pyc | Bin 0 -> 1159 bytes .../__pycache__/gdscript.cpython-310.pyc | Bin 0 -> 5117 bytes .../lexers/__pycache__/gleam.cpython-310.pyc | Bin 0 -> 2139 bytes .../lexers/__pycache__/go.cpython-310.pyc | Bin 0 -> 2701 bytes .../grammar_notation.cpython-310.pyc | Bin 0 -> 5971 bytes .../lexers/__pycache__/graph.cpython-310.pyc | Bin 0 -> 2958 bytes .../__pycache__/graphics.cpython-310.pyc | Bin 0 -> 24325 bytes .../__pycache__/graphql.cpython-310.pyc | Bin 0 -> 3177 bytes .../__pycache__/graphviz.cpython-310.pyc | Bin 0 -> 1779 bytes .../lexers/__pycache__/gsql.cpython-310.pyc | Bin 0 -> 3180 bytes .../lexers/__pycache__/hare.cpython-310.pyc | Bin 0 -> 2364 bytes .../__pycache__/haskell.cpython-310.pyc | Bin 0 -> 18882 bytes .../lexers/__pycache__/haxe.cpython-310.pyc | Bin 0 -> 14228 bytes .../lexers/__pycache__/hdl.cpython-310.pyc | Bin 0 -> 13576 bytes .../__pycache__/hexdump.cpython-310.pyc | Bin 0 -> 2715 bytes .../lexers/__pycache__/html.cpython-310.pyc | Bin 0 -> 13291 bytes .../lexers/__pycache__/idl.cpython-310.pyc | Bin 0 -> 11887 bytes .../lexers/__pycache__/igor.cpython-310.pyc | Bin 0 -> 25252 bytes .../__pycache__/inferno.cpython-310.pyc | Bin 0 -> 2594 bytes .../__pycache__/installers.cpython-310.pyc | Bin 0 -> 10631 bytes .../__pycache__/int_fiction.cpython-310.pyc | Bin 0 -> 29882 bytes .../lexers/__pycache__/iolang.cpython-310.pyc | Bin 0 -> 1729 bytes .../lexers/__pycache__/j.cpython-310.pyc | Bin 0 -> 3108 bytes .../__pycache__/javascript.cpython-310.pyc | Bin 0 -> 36783 bytes .../__pycache__/jmespath.cpython-310.pyc | Bin 0 -> 1794 bytes .../lexers/__pycache__/jslt.cpython-310.pyc | Bin 0 -> 2706 bytes .../lexers/__pycache__/json5.cpython-310.pyc | Bin 0 -> 2192 bytes .../__pycache__/jsonnet.cpython-310.pyc | Bin 0 -> 3368 bytes .../lexers/__pycache__/jsx.cpython-310.pyc | Bin 0 -> 2190 bytes .../lexers/__pycache__/julia.cpython-310.pyc | Bin 0 -> 6545 bytes .../lexers/__pycache__/jvm.cpython-310.pyc | Bin 0 -> 42826 bytes .../lexers/__pycache__/kuin.cpython-310.pyc | Bin 0 -> 6307 bytes .../lexers/__pycache__/kusto.cpython-310.pyc | Bin 0 -> 2438 bytes .../lexers/__pycache__/ldap.cpython-310.pyc | Bin 0 -> 4645 bytes .../lexers/__pycache__/lean.cpython-310.pyc | Bin 0 -> 5601 bytes .../__pycache__/lilypond.cpython-310.pyc | Bin 0 -> 4573 bytes .../lexers/__pycache__/lisp.cpython-310.pyc | Bin 0 -> 107518 bytes .../__pycache__/macaulay2.cpython-310.pyc | Bin 0 -> 22669 bytes .../lexers/__pycache__/make.cpython-310.pyc | Bin 0 -> 4872 bytes .../lexers/__pycache__/maple.cpython-310.pyc | Bin 0 -> 4076 bytes .../lexers/__pycache__/markup.cpython-310.pyc | Bin 0 -> 37951 bytes .../lexers/__pycache__/math.cpython-310.pyc | Bin 0 -> 1010 bytes .../lexers/__pycache__/matlab.cpython-310.pyc | Bin 0 -> 51859 bytes .../lexers/__pycache__/maxima.cpython-310.pyc | Bin 0 -> 2474 bytes .../lexers/__pycache__/meson.cpython-310.pyc | Bin 0 -> 2936 bytes .../lexers/__pycache__/mime.cpython-310.pyc | Bin 0 -> 6129 bytes .../__pycache__/minecraft.cpython-310.pyc | Bin 0 -> 6899 bytes .../lexers/__pycache__/mips.cpython-310.pyc | Bin 0 -> 2991 bytes .../lexers/__pycache__/ml.cpython-310.pyc | Bin 0 -> 15407 bytes .../__pycache__/modeling.cpython-310.pyc | Bin 0 -> 8709 bytes .../__pycache__/modula2.cpython-310.pyc | Bin 0 -> 19884 bytes .../lexers/__pycache__/mojo.cpython-310.pyc | Bin 0 -> 9426 bytes .../lexers/__pycache__/monte.cpython-310.pyc | Bin 0 -> 3689 bytes .../lexers/__pycache__/mosel.cpython-310.pyc | Bin 0 -> 6314 bytes .../lexers/__pycache__/ncl.cpython-310.pyc | Bin 0 -> 45413 bytes .../lexers/__pycache__/nimrod.cpython-310.pyc | Bin 0 -> 4199 bytes .../lexers/__pycache__/nit.cpython-310.pyc | Bin 0 -> 2341 bytes .../lexers/__pycache__/nix.cpython-310.pyc | Bin 0 -> 3388 bytes .../__pycache__/numbair.cpython-310.pyc | Bin 0 -> 1737 bytes .../lexers/__pycache__/oberon.cpython-310.pyc | Bin 0 -> 2980 bytes .../__pycache__/objective.cpython-310.pyc | Bin 0 -> 14891 bytes .../lexers/__pycache__/ooc.cpython-310.pyc | Bin 0 -> 2413 bytes .../__pycache__/openscad.cpython-310.pyc | Bin 0 -> 2827 bytes .../lexers/__pycache__/other.cpython-310.pyc | Bin 0 -> 2375 bytes .../__pycache__/parasail.cpython-310.pyc | Bin 0 -> 2329 bytes .../__pycache__/parsers.cpython-310.pyc | Bin 0 -> 16756 bytes .../lexers/__pycache__/pascal.cpython-310.pyc | Bin 0 -> 17911 bytes .../lexers/__pycache__/pawn.cpython-310.pyc | Bin 0 -> 5638 bytes .../lexers/__pycache__/pddl.cpython-310.pyc | Bin 0 -> 2478 bytes .../lexers/__pycache__/perl.cpython-310.pyc | Bin 0 -> 29005 bytes .../lexers/__pycache__/phix.cpython-310.pyc | Bin 0 -> 17761 bytes .../lexers/__pycache__/php.cpython-310.pyc | Bin 0 -> 9904 bytes .../__pycache__/pointless.cpython-310.pyc | Bin 0 -> 1827 bytes .../lexers/__pycache__/pony.cpython-310.pyc | Bin 0 -> 2564 bytes .../lexers/__pycache__/praat.cpython-310.pyc | Bin 0 -> 8134 bytes .../__pycache__/procfile.cpython-310.pyc | Bin 0 -> 1369 bytes .../lexers/__pycache__/prolog.cpython-310.pyc | Bin 0 -> 7909 bytes .../lexers/__pycache__/promql.cpython-310.pyc | Bin 0 -> 2766 bytes .../lexers/__pycache__/prql.cpython-310.pyc | Bin 0 -> 4985 bytes .../lexers/__pycache__/ptx.cpython-310.pyc | Bin 0 -> 3219 bytes .../lexers/__pycache__/python.cpython-310.pyc | Bin 0 -> 30156 bytes .../lexers/__pycache__/q.cpython-310.pyc | Bin 0 -> 4409 bytes .../lexers/__pycache__/qlik.cpython-310.pyc | Bin 0 -> 2370 bytes .../lexers/__pycache__/qvt.cpython-310.pyc | Bin 0 -> 4198 bytes .../lexers/__pycache__/r.cpython-310.pyc | Bin 0 -> 4538 bytes .../lexers/__pycache__/rdf.cpython-310.pyc | Bin 0 -> 8353 bytes .../lexers/__pycache__/rebol.cpython-310.pyc | Bin 0 -> 11648 bytes .../lexers/__pycache__/rego.cpython-310.pyc | Bin 0 -> 1594 bytes .../__pycache__/resource.cpython-310.pyc | Bin 0 -> 2392 bytes .../lexers/__pycache__/ride.cpython-310.pyc | Bin 0 -> 3740 bytes .../lexers/__pycache__/rita.cpython-310.pyc | Bin 0 -> 1302 bytes .../lexers/__pycache__/rnc.cpython-310.pyc | Bin 0 -> 1555 bytes .../__pycache__/roboconf.cpython-310.pyc | Bin 0 -> 1960 bytes .../robotframework.cpython-310.pyc | Bin 0 -> 19275 bytes .../lexers/__pycache__/ruby.cpython-310.pyc | Bin 0 -> 13393 bytes .../lexers/__pycache__/rust.cpython-310.pyc | Bin 0 -> 5018 bytes .../lexers/__pycache__/sas.cpython-310.pyc | Bin 0 -> 6024 bytes .../lexers/__pycache__/savi.cpython-310.pyc | Bin 0 -> 2559 bytes .../lexers/__pycache__/scdoc.cpython-310.pyc | Bin 0 -> 2156 bytes .../__pycache__/scripting.cpython-310.pyc | Bin 0 -> 55615 bytes .../lexers/__pycache__/sgf.cpython-310.pyc | Bin 0 -> 1774 bytes .../lexers/__pycache__/shell.cpython-310.pyc | Bin 0 -> 24647 bytes .../lexers/__pycache__/sieve.cpython-310.pyc | Bin 0 -> 2312 bytes .../lexers/__pycache__/slash.cpython-310.pyc | Bin 0 -> 4988 bytes .../__pycache__/smalltalk.cpython-310.pyc | Bin 0 -> 4503 bytes .../lexers/__pycache__/smithy.cpython-310.pyc | Bin 0 -> 2200 bytes .../lexers/__pycache__/smv.cpython-310.pyc | Bin 0 -> 2221 bytes .../lexers/__pycache__/snobol.cpython-310.pyc | Bin 0 -> 2077 bytes .../__pycache__/solidity.cpython-310.pyc | Bin 0 -> 2624 bytes .../lexers/__pycache__/soong.cpython-310.pyc | Bin 0 -> 1729 bytes .../lexers/__pycache__/sophia.cpython-310.pyc | Bin 0 -> 2731 bytes .../__pycache__/special.cpython-310.pyc | Bin 0 -> 3853 bytes .../lexers/__pycache__/spice.cpython-310.pyc | Bin 0 -> 2458 bytes .../lexers/__pycache__/sql.cpython-310.pyc | Bin 0 -> 24443 bytes .../__pycache__/srcinfo.cpython-310.pyc | Bin 0 -> 1782 bytes .../lexers/__pycache__/stata.cpython-310.pyc | Bin 0 -> 3065 bytes .../__pycache__/supercollider.cpython-310.pyc | Bin 0 -> 3103 bytes .../__pycache__/tablegen.cpython-310.pyc | Bin 0 -> 2624 bytes .../lexers/__pycache__/tact.cpython-310.pyc | Bin 0 -> 5798 bytes .../lexers/__pycache__/tal.cpython-310.pyc | Bin 0 -> 2175 bytes .../lexers/__pycache__/tcl.cpython-310.pyc | Bin 0 -> 3823 bytes .../lexers/__pycache__/teal.cpython-310.pyc | Bin 0 -> 2985 bytes .../__pycache__/templates.cpython-310.pyc | Bin 0 -> 57864 bytes .../__pycache__/teraterm.cpython-310.pyc | Bin 0 -> 4514 bytes .../__pycache__/testing.cpython-310.pyc | Bin 0 -> 8160 bytes .../lexers/__pycache__/text.cpython-310.pyc | Bin 0 -> 1505 bytes .../__pycache__/textedit.cpython-310.pyc | Bin 0 -> 6134 bytes .../__pycache__/textfmts.cpython-310.pyc | Bin 0 -> 10929 bytes .../__pycache__/theorem.cpython-310.pyc | Bin 0 -> 11854 bytes .../__pycache__/thingsdb.cpython-310.pyc | Bin 0 -> 4608 bytes .../lexers/__pycache__/tlb.cpython-310.pyc | Bin 0 -> 1435 bytes .../lexers/__pycache__/tls.cpython-310.pyc | Bin 0 -> 1584 bytes .../lexers/__pycache__/tnt.cpython-310.pyc | Bin 0 -> 6758 bytes .../__pycache__/trafficscript.cpython-310.pyc | Bin 0 -> 1587 bytes .../__pycache__/typoscript.cpython-310.pyc | Bin 0 -> 5402 bytes .../lexers/__pycache__/typst.cpython-310.pyc | Bin 0 -> 4575 bytes .../lexers/__pycache__/ul4.cpython-310.pyc | Bin 0 -> 5390 bytes .../lexers/__pycache__/unicon.cpython-310.pyc | Bin 0 -> 10203 bytes .../lexers/__pycache__/urbi.cpython-310.pyc | Bin 0 -> 4334 bytes .../lexers/__pycache__/usd.cpython-310.pyc | Bin 0 -> 2754 bytes .../__pycache__/varnish.cpython-310.pyc | Bin 0 -> 5191 bytes .../__pycache__/verification.cpython-310.pyc | Bin 0 -> 3008 bytes .../__pycache__/verifpal.cpython-310.pyc | Bin 0 -> 2134 bytes .../lexers/__pycache__/vip.cpython-310.pyc | Bin 0 -> 4350 bytes .../lexers/__pycache__/vyper.cpython-310.pyc | Bin 0 -> 3621 bytes .../lexers/__pycache__/web.cpython-310.pyc | Bin 0 -> 1260 bytes .../__pycache__/webassembly.cpython-310.pyc | Bin 0 -> 4661 bytes .../lexers/__pycache__/webidl.cpython-310.pyc | Bin 0 -> 5223 bytes .../__pycache__/webmisc.cpython-310.pyc | Bin 0 -> 24022 bytes .../lexers/__pycache__/wgsl.cpython-310.pyc | Bin 0 -> 9096 bytes .../lexers/__pycache__/whiley.cpython-310.pyc | Bin 0 -> 2573 bytes .../lexers/__pycache__/wowtoc.cpython-310.pyc | Bin 0 -> 2489 bytes .../lexers/__pycache__/wren.cpython-310.pyc | Bin 0 -> 2193 bytes .../lexers/__pycache__/x10.cpython-310.pyc | Bin 0 -> 1835 bytes .../lexers/__pycache__/xorg.cpython-310.pyc | Bin 0 -> 1150 bytes .../lexers/__pycache__/yang.cpython-310.pyc | Bin 0 -> 3166 bytes .../lexers/__pycache__/yara.cpython-310.pyc | Bin 0 -> 2080 bytes .../lexers/__pycache__/zig.cpython-310.pyc | Bin 0 -> 3017 bytes .../pygments/lexers/_ada_builtins.py | 103 + .../pygments/lexers/_asy_builtins.py | 1644 +++ .../pygments/lexers/_cl_builtins.py | 231 + .../pygments/lexers/_cocoa_builtins.py | 75 + .../pygments/lexers/_csound_builtins.py | 1780 ++++ .../pygments/lexers/_css_builtins.py | 558 ++ .../pygments/lexers/_googlesql_builtins.py | 918 ++ .../pygments/lexers/_julia_builtins.py | 411 + .../pygments/lexers/_lasso_builtins.py | 5326 ++++++++++ .../pygments/lexers/_lilypond_builtins.py | 4932 +++++++++ .../pygments/lexers/_lua_builtins.py | 285 + .../pygments/lexers/_luau_builtins.py | 62 + .../site-packages/pygments/lexers/_mapping.py | 602 ++ .../pygments/lexers/_mql_builtins.py | 1171 +++ .../pygments/lexers/_mysql_builtins.py | 1335 +++ .../pygments/lexers/_openedge_builtins.py | 2600 +++++ .../pygments/lexers/_php_builtins.py | 3325 +++++++ .../pygments/lexers/_postgres_builtins.py | 739 ++ .../pygments/lexers/_qlik_builtins.py | 666 ++ .../pygments/lexers/_scheme_builtins.py | 1609 +++ .../pygments/lexers/_scilab_builtins.py | 3093 ++++++ .../pygments/lexers/_sourcemod_builtins.py | 1151 +++ .../pygments/lexers/_sql_builtins.py | 106 + .../pygments/lexers/_stan_builtins.py | 648 ++ .../pygments/lexers/_stata_builtins.py | 457 + .../pygments/lexers/_tsql_builtins.py | 1003 ++ .../pygments/lexers/_usd_builtins.py | 112 + .../pygments/lexers/_vbscript_builtins.py | 279 + .../pygments/lexers/_vim_builtins.py | 1938 ++++ .../pygments/lexers/actionscript.py | 243 + .../site-packages/pygments/lexers/ada.py | 144 + .../site-packages/pygments/lexers/agile.py | 25 + .../site-packages/pygments/lexers/algebra.py | 299 + .../site-packages/pygments/lexers/ambient.py | 75 + .../site-packages/pygments/lexers/amdgpu.py | 54 + .../site-packages/pygments/lexers/ampl.py | 87 + .../site-packages/pygments/lexers/apdlexer.py | 593 ++ .../site-packages/pygments/lexers/apl.py | 103 + .../pygments/lexers/archetype.py | 315 + .../site-packages/pygments/lexers/arrow.py | 116 + .../site-packages/pygments/lexers/arturo.py | 249 + .../site-packages/pygments/lexers/asc.py | 55 + .../site-packages/pygments/lexers/asm.py | 1051 ++ .../site-packages/pygments/lexers/asn1.py | 178 + .../pygments/lexers/automation.py | 379 + .../site-packages/pygments/lexers/bare.py | 101 + .../site-packages/pygments/lexers/basic.py | 656 ++ .../site-packages/pygments/lexers/bdd.py | 57 + .../site-packages/pygments/lexers/berry.py | 99 + .../site-packages/pygments/lexers/bibtex.py | 159 + .../pygments/lexers/blueprint.py | 173 + .../site-packages/pygments/lexers/boa.py | 97 + .../site-packages/pygments/lexers/bqn.py | 112 + .../site-packages/pygments/lexers/business.py | 625 ++ .../site-packages/pygments/lexers/c_cpp.py | 414 + .../site-packages/pygments/lexers/c_like.py | 738 ++ .../pygments/lexers/capnproto.py | 74 + .../site-packages/pygments/lexers/carbon.py | 95 + .../site-packages/pygments/lexers/cddl.py | 172 + .../site-packages/pygments/lexers/chapel.py | 139 + .../site-packages/pygments/lexers/clean.py | 180 + .../site-packages/pygments/lexers/codeql.py | 80 + .../site-packages/pygments/lexers/comal.py | 81 + .../site-packages/pygments/lexers/compiled.py | 35 + .../site-packages/pygments/lexers/configs.py | 1433 +++ .../site-packages/pygments/lexers/console.py | 114 + .../site-packages/pygments/lexers/cplint.py | 43 + .../site-packages/pygments/lexers/crystal.py | 364 + .../site-packages/pygments/lexers/csound.py | 466 + .../site-packages/pygments/lexers/css.py | 602 ++ .../site-packages/pygments/lexers/d.py | 259 + .../site-packages/pygments/lexers/dalvik.py | 126 + .../site-packages/pygments/lexers/data.py | 763 ++ .../site-packages/pygments/lexers/dax.py | 135 + .../pygments/lexers/devicetree.py | 108 + .../site-packages/pygments/lexers/diff.py | 169 + .../site-packages/pygments/lexers/dns.py | 109 + .../site-packages/pygments/lexers/dotnet.py | 873 ++ .../site-packages/pygments/lexers/dsls.py | 970 ++ .../site-packages/pygments/lexers/dylan.py | 279 + .../site-packages/pygments/lexers/ecl.py | 144 + .../site-packages/pygments/lexers/eiffel.py | 68 + .../site-packages/pygments/lexers/elm.py | 123 + .../site-packages/pygments/lexers/elpi.py | 175 + .../site-packages/pygments/lexers/email.py | 132 + .../site-packages/pygments/lexers/erlang.py | 526 + .../site-packages/pygments/lexers/esoteric.py | 300 + .../site-packages/pygments/lexers/ezhil.py | 76 + .../site-packages/pygments/lexers/factor.py | 363 + .../site-packages/pygments/lexers/fantom.py | 251 + .../site-packages/pygments/lexers/felix.py | 275 + .../site-packages/pygments/lexers/fift.py | 68 + .../pygments/lexers/floscript.py | 81 + .../site-packages/pygments/lexers/forth.py | 178 + .../site-packages/pygments/lexers/fortran.py | 212 + .../site-packages/pygments/lexers/foxpro.py | 427 + .../site-packages/pygments/lexers/freefem.py | 893 ++ .../site-packages/pygments/lexers/func.py | 110 + .../pygments/lexers/functional.py | 21 + .../site-packages/pygments/lexers/futhark.py | 105 + .../pygments/lexers/gcodelexer.py | 35 + .../site-packages/pygments/lexers/gdscript.py | 189 + .../site-packages/pygments/lexers/gleam.py | 74 + .../site-packages/pygments/lexers/go.py | 97 + .../pygments/lexers/grammar_notation.py | 262 + .../site-packages/pygments/lexers/graph.py | 108 + .../site-packages/pygments/lexers/graphics.py | 794 ++ .../site-packages/pygments/lexers/graphql.py | 176 + .../site-packages/pygments/lexers/graphviz.py | 58 + .../site-packages/pygments/lexers/gsql.py | 103 + .../site-packages/pygments/lexers/hare.py | 73 + .../site-packages/pygments/lexers/haskell.py | 866 ++ .../site-packages/pygments/lexers/haxe.py | 935 ++ .../site-packages/pygments/lexers/hdl.py | 466 + .../site-packages/pygments/lexers/hexdump.py | 102 + .../site-packages/pygments/lexers/html.py | 670 ++ .../site-packages/pygments/lexers/idl.py | 284 + .../site-packages/pygments/lexers/igor.py | 435 + .../site-packages/pygments/lexers/inferno.py | 95 + .../pygments/lexers/installers.py | 352 + .../pygments/lexers/int_fiction.py | 1370 +++ .../site-packages/pygments/lexers/iolang.py | 61 + .../site-packages/pygments/lexers/j.py | 151 + .../pygments/lexers/javascript.py | 1591 +++ .../site-packages/pygments/lexers/jmespath.py | 69 + .../site-packages/pygments/lexers/jslt.py | 94 + .../site-packages/pygments/lexers/json5.py | 83 + .../site-packages/pygments/lexers/jsonnet.py | 169 + .../site-packages/pygments/lexers/jsx.py | 100 + .../site-packages/pygments/lexers/julia.py | 294 + .../site-packages/pygments/lexers/jvm.py | 1802 ++++ .../site-packages/pygments/lexers/kuin.py | 332 + .../site-packages/pygments/lexers/kusto.py | 93 + .../site-packages/pygments/lexers/ldap.py | 155 + .../site-packages/pygments/lexers/lean.py | 241 + .../site-packages/pygments/lexers/lilypond.py | 225 + .../site-packages/pygments/lexers/lisp.py | 3146 ++++++ .../pygments/lexers/macaulay2.py | 1814 ++++ .../site-packages/pygments/lexers/make.py | 212 + .../site-packages/pygments/lexers/maple.py | 291 + .../site-packages/pygments/lexers/markup.py | 1654 +++ .../site-packages/pygments/lexers/math.py | 21 + .../site-packages/pygments/lexers/matlab.py | 3307 ++++++ .../site-packages/pygments/lexers/maxima.py | 84 + .../site-packages/pygments/lexers/meson.py | 139 + .../site-packages/pygments/lexers/mime.py | 210 + .../pygments/lexers/minecraft.py | 391 + .../site-packages/pygments/lexers/mips.py | 130 + .../site-packages/pygments/lexers/ml.py | 958 ++ .../site-packages/pygments/lexers/modeling.py | 366 + .../site-packages/pygments/lexers/modula2.py | 1579 +++ .../site-packages/pygments/lexers/mojo.py | 707 ++ .../site-packages/pygments/lexers/monte.py | 203 + .../site-packages/pygments/lexers/mosel.py | 447 + .../site-packages/pygments/lexers/ncl.py | 894 ++ .../site-packages/pygments/lexers/nimrod.py | 199 + .../site-packages/pygments/lexers/nit.py | 63 + .../site-packages/pygments/lexers/nix.py | 144 + .../site-packages/pygments/lexers/numbair.py | 63 + .../site-packages/pygments/lexers/oberon.py | 120 + .../pygments/lexers/objective.py | 513 + .../site-packages/pygments/lexers/ooc.py | 84 + .../site-packages/pygments/lexers/openscad.py | 96 + .../site-packages/pygments/lexers/other.py | 41 + .../site-packages/pygments/lexers/parasail.py | 78 + .../site-packages/pygments/lexers/parsers.py | 798 ++ .../site-packages/pygments/lexers/pascal.py | 644 ++ .../site-packages/pygments/lexers/pawn.py | 202 + .../site-packages/pygments/lexers/pddl.py | 82 + .../site-packages/pygments/lexers/perl.py | 733 ++ .../site-packages/pygments/lexers/phix.py | 363 + .../site-packages/pygments/lexers/php.py | 334 + .../pygments/lexers/pointless.py | 70 + .../site-packages/pygments/lexers/pony.py | 93 + .../site-packages/pygments/lexers/praat.py | 303 + .../site-packages/pygments/lexers/procfile.py | 41 + .../site-packages/pygments/lexers/prolog.py | 318 + .../site-packages/pygments/lexers/promql.py | 176 + .../site-packages/pygments/lexers/prql.py | 251 + .../site-packages/pygments/lexers/ptx.py | 119 + .../site-packages/pygments/lexers/python.py | 1201 +++ .../site-packages/pygments/lexers/q.py | 187 + .../site-packages/pygments/lexers/qlik.py | 117 + .../site-packages/pygments/lexers/qvt.py | 153 + .../site-packages/pygments/lexers/r.py | 196 + .../site-packages/pygments/lexers/rdf.py | 468 + .../site-packages/pygments/lexers/rebol.py | 419 + .../site-packages/pygments/lexers/rego.py | 57 + .../site-packages/pygments/lexers/resource.py | 83 + .../site-packages/pygments/lexers/ride.py | 138 + .../site-packages/pygments/lexers/rita.py | 42 + .../site-packages/pygments/lexers/rnc.py | 66 + .../site-packages/pygments/lexers/roboconf.py | 81 + .../pygments/lexers/robotframework.py | 551 + .../site-packages/pygments/lexers/ruby.py | 518 + .../site-packages/pygments/lexers/rust.py | 222 + .../site-packages/pygments/lexers/sas.py | 227 + .../site-packages/pygments/lexers/savi.py | 171 + .../site-packages/pygments/lexers/scdoc.py | 85 + .../pygments/lexers/scripting.py | 1616 +++ .../site-packages/pygments/lexers/sgf.py | 59 + .../site-packages/pygments/lexers/shell.py | 902 ++ .../site-packages/pygments/lexers/sieve.py | 78 + .../site-packages/pygments/lexers/slash.py | 183 + .../pygments/lexers/smalltalk.py | 194 + .../site-packages/pygments/lexers/smithy.py | 77 + .../site-packages/pygments/lexers/smv.py | 78 + .../site-packages/pygments/lexers/snobol.py | 82 + .../site-packages/pygments/lexers/solidity.py | 87 + .../site-packages/pygments/lexers/soong.py | 78 + .../site-packages/pygments/lexers/sophia.py | 102 + .../site-packages/pygments/lexers/special.py | 122 + .../site-packages/pygments/lexers/spice.py | 70 + .../site-packages/pygments/lexers/sql.py | 1109 +++ .../site-packages/pygments/lexers/srcinfo.py | 62 + .../site-packages/pygments/lexers/stata.py | 170 + .../pygments/lexers/supercollider.py | 94 + .../site-packages/pygments/lexers/tablegen.py | 177 + .../site-packages/pygments/lexers/tact.py | 303 + .../site-packages/pygments/lexers/tal.py | 77 + .../site-packages/pygments/lexers/tcl.py | 148 + .../site-packages/pygments/lexers/teal.py | 88 + .../pygments/lexers/templates.py | 2355 +++++ .../site-packages/pygments/lexers/teraterm.py | 325 + .../site-packages/pygments/lexers/testing.py | 209 + .../site-packages/pygments/lexers/text.py | 27 + .../site-packages/pygments/lexers/textedit.py | 205 + .../site-packages/pygments/lexers/textfmts.py | 436 + .../site-packages/pygments/lexers/theorem.py | 410 + .../site-packages/pygments/lexers/thingsdb.py | 140 + .../site-packages/pygments/lexers/tlb.py | 59 + .../site-packages/pygments/lexers/tls.py | 54 + .../site-packages/pygments/lexers/tnt.py | 270 + .../pygments/lexers/trafficscript.py | 51 + .../pygments/lexers/typoscript.py | 216 + .../site-packages/pygments/lexers/typst.py | 160 + .../site-packages/pygments/lexers/ul4.py | 309 + .../site-packages/pygments/lexers/unicon.py | 413 + .../site-packages/pygments/lexers/urbi.py | 145 + .../site-packages/pygments/lexers/usd.py | 85 + .../site-packages/pygments/lexers/varnish.py | 189 + .../pygments/lexers/verification.py | 113 + .../site-packages/pygments/lexers/verifpal.py | 65 + .../site-packages/pygments/lexers/vip.py | 150 + .../site-packages/pygments/lexers/vyper.py | 140 + .../site-packages/pygments/lexers/web.py | 24 + .../pygments/lexers/webassembly.py | 119 + .../site-packages/pygments/lexers/webidl.py | 298 + .../site-packages/pygments/lexers/webmisc.py | 1006 ++ .../site-packages/pygments/lexers/wgsl.py | 406 + .../site-packages/pygments/lexers/whiley.py | 115 + .../site-packages/pygments/lexers/wowtoc.py | 120 + .../site-packages/pygments/lexers/wren.py | 98 + .../site-packages/pygments/lexers/x10.py | 66 + .../site-packages/pygments/lexers/xorg.py | 38 + .../site-packages/pygments/lexers/yang.py | 103 + .../site-packages/pygments/lexers/yara.py | 69 + .../site-packages/pygments/lexers/zig.py | 125 + .../site-packages/pygments/modeline.py | 43 + .../site-packages/pygments/plugin.py | 72 + .../site-packages/pygments/regexopt.py | 91 + .../site-packages/pygments/scanner.py | 104 + .../site-packages/pygments/sphinxext.py | 247 + .../site-packages/pygments/style.py | 203 + .../site-packages/pygments/styles/__init__.py | 61 + .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 2012 bytes .../__pycache__/_mapping.cpython-310.pyc | Bin 0 -> 3298 bytes .../styles/__pycache__/abap.cpython-310.pyc | Bin 0 -> 947 bytes .../styles/__pycache__/algol.cpython-310.pyc | Bin 0 -> 2218 bytes .../__pycache__/algol_nu.cpython-310.pyc | Bin 0 -> 2233 bytes .../__pycache__/arduino.cpython-310.pyc | Bin 0 -> 2302 bytes .../styles/__pycache__/autumn.cpython-310.pyc | Bin 0 -> 1901 bytes .../__pycache__/borland.cpython-310.pyc | Bin 0 -> 1612 bytes .../styles/__pycache__/bw.cpython-310.pyc | Bin 0 -> 1306 bytes .../styles/__pycache__/coffee.cpython-310.pyc | Bin 0 -> 2131 bytes .../__pycache__/colorful.cpython-310.pyc | Bin 0 -> 2300 bytes .../__pycache__/default.cpython-310.pyc | Bin 0 -> 2094 bytes .../__pycache__/dracula.cpython-310.pyc | Bin 0 -> 2232 bytes .../styles/__pycache__/emacs.cpython-310.pyc | Bin 0 -> 2116 bytes .../__pycache__/friendly.cpython-310.pyc | Bin 0 -> 2203 bytes .../friendly_grayscale.cpython-310.pyc | Bin 0 -> 2417 bytes .../styles/__pycache__/fruity.cpython-310.pyc | Bin 0 -> 1478 bytes .../__pycache__/gh_dark.cpython-310.pyc | Bin 0 -> 2683 bytes .../__pycache__/gruvbox.cpython-310.pyc | Bin 0 -> 2791 bytes .../styles/__pycache__/igor.cpython-310.pyc | Bin 0 -> 979 bytes .../styles/__pycache__/inkpot.cpython-310.pyc | Bin 0 -> 1956 bytes .../__pycache__/lightbulb.cpython-310.pyc | Bin 0 -> 2691 bytes .../__pycache__/lilypond.cpython-310.pyc | Bin 0 -> 1735 bytes .../__pycache__/lovelace.cpython-310.pyc | Bin 0 -> 2648 bytes .../styles/__pycache__/manni.cpython-310.pyc | Bin 0 -> 2344 bytes .../__pycache__/material.cpython-310.pyc | Bin 0 -> 2795 bytes .../__pycache__/monokai.cpython-310.pyc | Bin 0 -> 2590 bytes .../styles/__pycache__/murphy.cpython-310.pyc | Bin 0 -> 2265 bytes .../styles/__pycache__/native.cpython-310.pyc | Bin 0 -> 2007 bytes .../styles/__pycache__/nord.cpython-310.pyc | Bin 0 -> 3373 bytes .../__pycache__/onedark.cpython-310.pyc | Bin 0 -> 1637 bytes .../__pycache__/paraiso_dark.cpython-310.pyc | Bin 0 -> 2878 bytes .../__pycache__/paraiso_light.cpython-310.pyc | Bin 0 -> 2884 bytes .../styles/__pycache__/pastie.cpython-310.pyc | Bin 0 -> 2200 bytes .../__pycache__/perldoc.cpython-310.pyc | Bin 0 -> 2029 bytes .../__pycache__/rainbow_dash.cpython-310.pyc | Bin 0 -> 2487 bytes .../styles/__pycache__/rrt.cpython-310.pyc | Bin 0 -> 1219 bytes .../styles/__pycache__/sas.cpython-310.pyc | Bin 0 -> 1525 bytes .../__pycache__/solarized.cpython-310.pyc | Bin 0 -> 3297 bytes .../__pycache__/staroffice.cpython-310.pyc | Bin 0 -> 1026 bytes .../__pycache__/stata_dark.cpython-310.pyc | Bin 0 -> 1395 bytes .../__pycache__/stata_light.cpython-310.pyc | Bin 0 -> 1423 bytes .../styles/__pycache__/tango.cpython-310.pyc | Bin 0 -> 3920 bytes .../styles/__pycache__/trac.cpython-310.pyc | Bin 0 -> 1782 bytes .../styles/__pycache__/vim.cpython-310.pyc | Bin 0 -> 1727 bytes .../styles/__pycache__/vs.cpython-310.pyc | Bin 0 -> 1147 bytes .../styles/__pycache__/xcode.cpython-310.pyc | Bin 0 -> 1345 bytes .../__pycache__/zenburn.cpython-310.pyc | Bin 0 -> 2316 bytes .../site-packages/pygments/styles/_mapping.py | 54 + .../site-packages/pygments/styles/abap.py | 32 + .../site-packages/pygments/styles/algol.py | 65 + .../site-packages/pygments/styles/algol_nu.py | 65 + .../site-packages/pygments/styles/arduino.py | 100 + .../site-packages/pygments/styles/autumn.py | 67 + .../site-packages/pygments/styles/borland.py | 53 + .../site-packages/pygments/styles/bw.py | 52 + .../site-packages/pygments/styles/coffee.py | 80 + .../site-packages/pygments/styles/colorful.py | 83 + .../site-packages/pygments/styles/default.py | 76 + .../site-packages/pygments/styles/dracula.py | 90 + .../site-packages/pygments/styles/emacs.py | 75 + .../site-packages/pygments/styles/friendly.py | 76 + .../pygments/styles/friendly_grayscale.py | 80 + .../site-packages/pygments/styles/fruity.py | 47 + .../site-packages/pygments/styles/gh_dark.py | 113 + .../site-packages/pygments/styles/gruvbox.py | 118 + .../site-packages/pygments/styles/igor.py | 32 + .../site-packages/pygments/styles/inkpot.py | 72 + .../pygments/styles/lightbulb.py | 110 + .../site-packages/pygments/styles/lilypond.py | 62 + .../site-packages/pygments/styles/lovelace.py | 100 + .../site-packages/pygments/styles/manni.py | 79 + .../site-packages/pygments/styles/material.py | 124 + .../site-packages/pygments/styles/monokai.py | 112 + .../site-packages/pygments/styles/murphy.py | 82 + .../site-packages/pygments/styles/native.py | 70 + .../site-packages/pygments/styles/nord.py | 156 + .../site-packages/pygments/styles/onedark.py | 63 + .../pygments/styles/paraiso_dark.py | 124 + .../pygments/styles/paraiso_light.py | 124 + .../site-packages/pygments/styles/pastie.py | 78 + .../site-packages/pygments/styles/perldoc.py | 73 + .../pygments/styles/rainbow_dash.py | 95 + .../site-packages/pygments/styles/rrt.py | 40 + .../site-packages/pygments/styles/sas.py | 46 + .../pygments/styles/solarized.py | 144 + .../pygments/styles/staroffice.py | 31 + .../pygments/styles/stata_dark.py | 42 + .../pygments/styles/stata_light.py | 42 + .../site-packages/pygments/styles/tango.py | 143 + .../site-packages/pygments/styles/trac.py | 66 + .../site-packages/pygments/styles/vim.py | 67 + .../site-packages/pygments/styles/vs.py | 41 + .../site-packages/pygments/styles/xcode.py | 53 + .../site-packages/pygments/styles/zenburn.py | 83 + .../site-packages/pygments/token.py | 214 + .../site-packages/pygments/unistring.py | 153 + .../python3.10/site-packages/pygments/util.py | 324 + .../pytest-7.4.3.dist-info/RECORD | 154 - .../pytest-9.0.2.dist-info/INSTALLER | 1 + .../METADATA | 78 +- .../pytest-9.0.2.dist-info/RECORD | 159 + .../pytest-9.0.2.dist-info/WHEEL | 5 + .../entry_points.txt | 0 .../licenses}/LICENSE | 0 .../top_level.txt | 0 .../site-packages/pytest/__init__.py | 93 +- .../site-packages/pytest/__main__.py | 4 + .../__pycache__/__init__.cpython-310.pyc | Bin 4184 -> 4363 bytes .../__pycache__/__main__.cpython-310.pyc | Bin 306 -> 352 bytes .../pytest_asyncio-1.3.0.dist-info/INSTALLER | 1 + .../pytest_asyncio-1.3.0.dist-info/METADATA | 91 + .../pytest_asyncio-1.3.0.dist-info/RECORD | 13 + .../pytest_asyncio-1.3.0.dist-info/REQUESTED | 0 .../pytest_asyncio-1.3.0.dist-info/WHEEL | 5 + .../entry_points.txt | 2 + .../licenses/LICENSE | 201 + .../top_level.txt | 1 + .../site-packages/pytest_asyncio/__init__.py | 11 + .../__init__.cpython-310-pytest-9.0.2.pyc | Bin 0 -> 571 bytes .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 462 bytes .../plugin.cpython-310-pytest-9.0.2.pyc | Bin 0 -> 28903 bytes .../__pycache__/plugin.cpython-310.pyc | Bin 0 -> 27180 bytes .../site-packages/pytest_asyncio/plugin.py | 896 ++ .../site-packages/pytest_asyncio/py.typed | 0 .../yarl-1.22.0.dist-info/INSTALLER | 1 + .../yarl-1.22.0.dist-info/METADATA | 2478 +++++ .../yarl-1.22.0.dist-info/RECORD | 26 + .../site-packages/yarl-1.22.0.dist-info/WHEEL | 7 + .../yarl-1.22.0.dist-info/licenses/LICENSE | 202 + .../yarl-1.22.0.dist-info/licenses/NOTICE | 13 + .../yarl-1.22.0.dist-info/top_level.txt | 1 + .../python3.10/site-packages/yarl/__init__.py | 14 + .../yarl/__pycache__/__init__.cpython-310.pyc | Bin 0 -> 436 bytes .../yarl/__pycache__/_parse.cpython-310.pyc | Bin 0 -> 4475 bytes .../yarl/__pycache__/_path.cpython-310.pyc | Bin 0 -> 986 bytes .../yarl/__pycache__/_query.cpython-310.pyc | Bin 0 -> 3418 bytes .../yarl/__pycache__/_quoters.cpython-310.pyc | Bin 0 -> 1426 bytes .../yarl/__pycache__/_quoting.cpython-310.pyc | Bin 0 -> 560 bytes .../__pycache__/_quoting_py.cpython-310.pyc | Bin 0 -> 4820 bytes .../yarl/__pycache__/_url.cpython-310.pyc | Bin 0 -> 38877 bytes .../python3.10/site-packages/yarl/_parse.py | 203 + .../python3.10/site-packages/yarl/_path.py | 41 + .../python3.10/site-packages/yarl/_query.py | 121 + .../python3.10/site-packages/yarl/_quoters.py | 33 + .../python3.10/site-packages/yarl/_quoting.py | 19 + ..._quoting_c.cpython-310-x86_64-linux-gnu.so | Bin 0 -> 1031960 bytes .../site-packages/yarl/_quoting_c.pyx | 451 + .../site-packages/yarl/_quoting_py.py | 213 + .../lib/python3.10/site-packages/yarl/_url.py | 1622 +++ .../python3.10/site-packages/yarl/py.typed | 1 + walkthrough.md | 28 +- 1273 files changed, 206095 insertions(+), 18491 deletions(-) create mode 100644 src/browser/__pycache__/__init__.cpython-310.pyc create mode 100644 src/browser/__pycache__/manager.cpython-310.pyc create mode 100644 src/extractor/__pycache__/__init__.cpython-310.pyc create mode 100644 src/extractor/__pycache__/client.cpython-310.pyc create mode 100644 tests/__pycache__/__init__.cpython-310.pyc create mode 100644 tests/e2e/__pycache__/test_handover.cpython-310-pytest-7.4.3.pyc create mode 100644 tests/e2e/__pycache__/test_handover.cpython-310-pytest-9.0.2.pyc create mode 100755 venv/bin/curl-cffi create mode 100755 venv/bin/pygmentize delete mode 100644 venv/lib/python3.10/site-packages/_pytest/__pycache__/nose.cpython-310.pyc delete mode 100644 venv/lib/python3.10/site-packages/_pytest/__pycache__/python_path.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/_pytest/__pycache__/raises.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/_pytest/__pycache__/subtests.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/_pytest/__pycache__/terminalprogress.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/_pytest/__pycache__/tracemalloc.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/_pytest/_io/__pycache__/pprint.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/_pytest/_io/pprint.py delete mode 100644 venv/lib/python3.10/site-packages/_pytest/nose.py delete mode 100644 venv/lib/python3.10/site-packages/_pytest/python_path.py create mode 100644 venv/lib/python3.10/site-packages/_pytest/raises.py create mode 100644 venv/lib/python3.10/site-packages/_pytest/subtests.py create mode 100644 venv/lib/python3.10/site-packages/_pytest/terminalprogress.py create mode 100644 venv/lib/python3.10/site-packages/_pytest/tracemalloc.py rename venv/lib/python3.10/site-packages/{async_timeout-5.0.1.dist-info => aiohappyeyeballs-2.6.1.dist-info}/INSTALLER (100%) create mode 100644 venv/lib/python3.10/site-packages/aiohappyeyeballs-2.6.1.dist-info/LICENSE create mode 100644 venv/lib/python3.10/site-packages/aiohappyeyeballs-2.6.1.dist-info/METADATA create mode 100644 venv/lib/python3.10/site-packages/aiohappyeyeballs-2.6.1.dist-info/RECORD create mode 100644 venv/lib/python3.10/site-packages/aiohappyeyeballs-2.6.1.dist-info/WHEEL create mode 100644 venv/lib/python3.10/site-packages/aiohappyeyeballs/__init__.py create mode 100644 venv/lib/python3.10/site-packages/aiohappyeyeballs/__pycache__/__init__.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/aiohappyeyeballs/__pycache__/_staggered.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/aiohappyeyeballs/__pycache__/impl.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/aiohappyeyeballs/__pycache__/types.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/aiohappyeyeballs/__pycache__/utils.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/aiohappyeyeballs/_staggered.py create mode 100644 venv/lib/python3.10/site-packages/aiohappyeyeballs/impl.py rename venv/lib/python3.10/site-packages/{curl_cffi-0.5.10.dist-info/REQUESTED => aiohappyeyeballs/py.typed} (100%) create mode 100644 venv/lib/python3.10/site-packages/aiohappyeyeballs/types.py create mode 100644 venv/lib/python3.10/site-packages/aiohappyeyeballs/utils.py rename venv/lib/python3.10/site-packages/{curl_cffi-0.5.10.dist-info => aiohttp-3.9.1.dist-info}/INSTALLER (100%) create mode 100644 venv/lib/python3.10/site-packages/aiohttp-3.9.1.dist-info/LICENSE.txt create mode 100644 venv/lib/python3.10/site-packages/aiohttp-3.9.1.dist-info/METADATA create mode 100644 venv/lib/python3.10/site-packages/aiohttp-3.9.1.dist-info/RECORD rename venv/lib/python3.10/site-packages/{pytest-7.4.3.dist-info => aiohttp-3.9.1.dist-info}/REQUESTED (100%) create mode 100644 venv/lib/python3.10/site-packages/aiohttp-3.9.1.dist-info/WHEEL create mode 100644 venv/lib/python3.10/site-packages/aiohttp-3.9.1.dist-info/top_level.txt create mode 100644 venv/lib/python3.10/site-packages/aiohttp/.hash/_cparser.pxd.hash create mode 100644 venv/lib/python3.10/site-packages/aiohttp/.hash/_find_header.pxd.hash create mode 100644 venv/lib/python3.10/site-packages/aiohttp/.hash/_helpers.pyi.hash create mode 100644 venv/lib/python3.10/site-packages/aiohttp/.hash/_helpers.pyx.hash create mode 100644 venv/lib/python3.10/site-packages/aiohttp/.hash/_http_parser.pyx.hash create mode 100644 venv/lib/python3.10/site-packages/aiohttp/.hash/_http_writer.pyx.hash create mode 100644 venv/lib/python3.10/site-packages/aiohttp/.hash/_websocket.pyx.hash create mode 100644 venv/lib/python3.10/site-packages/aiohttp/.hash/hdrs.py.hash create mode 100644 venv/lib/python3.10/site-packages/aiohttp/__init__.py create mode 100644 venv/lib/python3.10/site-packages/aiohttp/__pycache__/__init__.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/aiohttp/__pycache__/abc.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/aiohttp/__pycache__/base_protocol.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/aiohttp/__pycache__/client.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/aiohttp/__pycache__/client_exceptions.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/aiohttp/__pycache__/client_proto.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/aiohttp/__pycache__/client_reqrep.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/aiohttp/__pycache__/client_ws.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/aiohttp/__pycache__/compression_utils.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/aiohttp/__pycache__/connector.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/aiohttp/__pycache__/cookiejar.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/aiohttp/__pycache__/formdata.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/aiohttp/__pycache__/hdrs.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/aiohttp/__pycache__/helpers.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/aiohttp/__pycache__/http.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/aiohttp/__pycache__/http_exceptions.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/aiohttp/__pycache__/http_parser.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/aiohttp/__pycache__/http_websocket.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/aiohttp/__pycache__/http_writer.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/aiohttp/__pycache__/locks.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/aiohttp/__pycache__/log.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/aiohttp/__pycache__/multipart.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/aiohttp/__pycache__/payload.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/aiohttp/__pycache__/payload_streamer.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/aiohttp/__pycache__/pytest_plugin.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/aiohttp/__pycache__/resolver.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/aiohttp/__pycache__/streams.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/aiohttp/__pycache__/tcp_helpers.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/aiohttp/__pycache__/test_utils.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/aiohttp/__pycache__/tracing.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/aiohttp/__pycache__/typedefs.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/aiohttp/__pycache__/web.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/aiohttp/__pycache__/web_app.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/aiohttp/__pycache__/web_exceptions.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/aiohttp/__pycache__/web_fileresponse.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/aiohttp/__pycache__/web_log.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/aiohttp/__pycache__/web_middlewares.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/aiohttp/__pycache__/web_protocol.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/aiohttp/__pycache__/web_request.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/aiohttp/__pycache__/web_response.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/aiohttp/__pycache__/web_routedef.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/aiohttp/__pycache__/web_runner.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/aiohttp/__pycache__/web_server.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/aiohttp/__pycache__/web_urldispatcher.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/aiohttp/__pycache__/web_ws.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/aiohttp/__pycache__/worker.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/aiohttp/_cparser.pxd create mode 100644 venv/lib/python3.10/site-packages/aiohttp/_find_header.pxd create mode 100644 venv/lib/python3.10/site-packages/aiohttp/_headers.pxi create mode 100755 venv/lib/python3.10/site-packages/aiohttp/_helpers.cpython-310-x86_64-linux-gnu.so create mode 100644 venv/lib/python3.10/site-packages/aiohttp/_helpers.pyi create mode 100644 venv/lib/python3.10/site-packages/aiohttp/_helpers.pyx create mode 100755 venv/lib/python3.10/site-packages/aiohttp/_http_parser.cpython-310-x86_64-linux-gnu.so create mode 100644 venv/lib/python3.10/site-packages/aiohttp/_http_parser.pyx create mode 100755 venv/lib/python3.10/site-packages/aiohttp/_http_writer.cpython-310-x86_64-linux-gnu.so create mode 100644 venv/lib/python3.10/site-packages/aiohttp/_http_writer.pyx create mode 100755 venv/lib/python3.10/site-packages/aiohttp/_websocket.cpython-310-x86_64-linux-gnu.so create mode 100644 venv/lib/python3.10/site-packages/aiohttp/_websocket.pyx create mode 100644 venv/lib/python3.10/site-packages/aiohttp/abc.py create mode 100644 venv/lib/python3.10/site-packages/aiohttp/base_protocol.py create mode 100644 venv/lib/python3.10/site-packages/aiohttp/client.py create mode 100644 venv/lib/python3.10/site-packages/aiohttp/client_exceptions.py create mode 100644 venv/lib/python3.10/site-packages/aiohttp/client_proto.py create mode 100644 venv/lib/python3.10/site-packages/aiohttp/client_reqrep.py create mode 100644 venv/lib/python3.10/site-packages/aiohttp/client_ws.py create mode 100644 venv/lib/python3.10/site-packages/aiohttp/compression_utils.py create mode 100644 venv/lib/python3.10/site-packages/aiohttp/connector.py create mode 100644 venv/lib/python3.10/site-packages/aiohttp/cookiejar.py create mode 100644 venv/lib/python3.10/site-packages/aiohttp/formdata.py create mode 100644 venv/lib/python3.10/site-packages/aiohttp/hdrs.py create mode 100644 venv/lib/python3.10/site-packages/aiohttp/helpers.py create mode 100644 venv/lib/python3.10/site-packages/aiohttp/http.py create mode 100644 venv/lib/python3.10/site-packages/aiohttp/http_exceptions.py create mode 100644 venv/lib/python3.10/site-packages/aiohttp/http_parser.py create mode 100644 venv/lib/python3.10/site-packages/aiohttp/http_websocket.py create mode 100644 venv/lib/python3.10/site-packages/aiohttp/http_writer.py create mode 100644 venv/lib/python3.10/site-packages/aiohttp/locks.py create mode 100644 venv/lib/python3.10/site-packages/aiohttp/log.py create mode 100644 venv/lib/python3.10/site-packages/aiohttp/multipart.py create mode 100644 venv/lib/python3.10/site-packages/aiohttp/payload.py create mode 100644 venv/lib/python3.10/site-packages/aiohttp/payload_streamer.py create mode 100644 venv/lib/python3.10/site-packages/aiohttp/py.typed create mode 100644 venv/lib/python3.10/site-packages/aiohttp/pytest_plugin.py create mode 100644 venv/lib/python3.10/site-packages/aiohttp/resolver.py create mode 100644 venv/lib/python3.10/site-packages/aiohttp/streams.py create mode 100644 venv/lib/python3.10/site-packages/aiohttp/tcp_helpers.py create mode 100644 venv/lib/python3.10/site-packages/aiohttp/test_utils.py create mode 100644 venv/lib/python3.10/site-packages/aiohttp/tracing.py create mode 100644 venv/lib/python3.10/site-packages/aiohttp/typedefs.py create mode 100644 venv/lib/python3.10/site-packages/aiohttp/web.py create mode 100644 venv/lib/python3.10/site-packages/aiohttp/web_app.py create mode 100644 venv/lib/python3.10/site-packages/aiohttp/web_exceptions.py create mode 100644 venv/lib/python3.10/site-packages/aiohttp/web_fileresponse.py create mode 100644 venv/lib/python3.10/site-packages/aiohttp/web_log.py create mode 100644 venv/lib/python3.10/site-packages/aiohttp/web_middlewares.py create mode 100644 venv/lib/python3.10/site-packages/aiohttp/web_protocol.py create mode 100644 venv/lib/python3.10/site-packages/aiohttp/web_request.py create mode 100644 venv/lib/python3.10/site-packages/aiohttp/web_response.py create mode 100644 venv/lib/python3.10/site-packages/aiohttp/web_routedef.py create mode 100644 venv/lib/python3.10/site-packages/aiohttp/web_runner.py create mode 100644 venv/lib/python3.10/site-packages/aiohttp/web_server.py create mode 100644 venv/lib/python3.10/site-packages/aiohttp/web_urldispatcher.py create mode 100644 venv/lib/python3.10/site-packages/aiohttp/web_ws.py create mode 100644 venv/lib/python3.10/site-packages/aiohttp/worker.py rename venv/lib/python3.10/site-packages/{pytest-7.4.3.dist-info => aiosignal-1.4.0.dist-info}/INSTALLER (100%) create mode 100644 venv/lib/python3.10/site-packages/aiosignal-1.4.0.dist-info/METADATA create mode 100644 venv/lib/python3.10/site-packages/aiosignal-1.4.0.dist-info/RECORD rename venv/lib/python3.10/site-packages/{async_timeout-5.0.1.dist-info => aiosignal-1.4.0.dist-info}/WHEEL (65%) create mode 100644 venv/lib/python3.10/site-packages/aiosignal-1.4.0.dist-info/licenses/LICENSE create mode 100644 venv/lib/python3.10/site-packages/aiosignal-1.4.0.dist-info/top_level.txt create mode 100644 venv/lib/python3.10/site-packages/aiosignal/__init__.py create mode 100644 venv/lib/python3.10/site-packages/aiosignal/__pycache__/__init__.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/aiosignal/py.typed create mode 100644 venv/lib/python3.10/site-packages/async_timeout-4.0.3.dist-info/INSTALLER rename venv/lib/python3.10/site-packages/{async_timeout-5.0.1.dist-info => async_timeout-4.0.3.dist-info}/LICENSE (100%) rename venv/lib/python3.10/site-packages/{async_timeout-5.0.1.dist-info => async_timeout-4.0.3.dist-info}/METADATA (69%) create mode 100644 venv/lib/python3.10/site-packages/async_timeout-4.0.3.dist-info/RECORD rename venv/lib/python3.10/site-packages/{pytest-7.4.3.dist-info => async_timeout-4.0.3.dist-info}/WHEEL (65%) rename venv/lib/python3.10/site-packages/{async_timeout-5.0.1.dist-info => async_timeout-4.0.3.dist-info}/top_level.txt (100%) rename venv/lib/python3.10/site-packages/{async_timeout-5.0.1.dist-info => async_timeout-4.0.3.dist-info}/zip-safe (100%) delete mode 100644 venv/lib/python3.10/site-packages/async_timeout-5.0.1.dist-info/RECORD create mode 100644 venv/lib/python3.10/site-packages/attr/__init__.py create mode 100644 venv/lib/python3.10/site-packages/attr/__init__.pyi create mode 100644 venv/lib/python3.10/site-packages/attr/__pycache__/__init__.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/attr/__pycache__/_cmp.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/attr/__pycache__/_compat.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/attr/__pycache__/_config.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/attr/__pycache__/_funcs.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/attr/__pycache__/_make.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/attr/__pycache__/_next_gen.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/attr/__pycache__/_version_info.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/attr/__pycache__/converters.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/attr/__pycache__/exceptions.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/attr/__pycache__/filters.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/attr/__pycache__/setters.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/attr/__pycache__/validators.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/attr/_cmp.py create mode 100644 venv/lib/python3.10/site-packages/attr/_cmp.pyi create mode 100644 venv/lib/python3.10/site-packages/attr/_compat.py create mode 100644 venv/lib/python3.10/site-packages/attr/_config.py create mode 100644 venv/lib/python3.10/site-packages/attr/_funcs.py create mode 100644 venv/lib/python3.10/site-packages/attr/_make.py create mode 100644 venv/lib/python3.10/site-packages/attr/_next_gen.py create mode 100644 venv/lib/python3.10/site-packages/attr/_typing_compat.pyi create mode 100644 venv/lib/python3.10/site-packages/attr/_version_info.py create mode 100644 venv/lib/python3.10/site-packages/attr/_version_info.pyi create mode 100644 venv/lib/python3.10/site-packages/attr/converters.py create mode 100644 venv/lib/python3.10/site-packages/attr/converters.pyi create mode 100644 venv/lib/python3.10/site-packages/attr/exceptions.py create mode 100644 venv/lib/python3.10/site-packages/attr/exceptions.pyi create mode 100644 venv/lib/python3.10/site-packages/attr/filters.py create mode 100644 venv/lib/python3.10/site-packages/attr/filters.pyi create mode 100644 venv/lib/python3.10/site-packages/attr/py.typed create mode 100644 venv/lib/python3.10/site-packages/attr/setters.py create mode 100644 venv/lib/python3.10/site-packages/attr/setters.pyi create mode 100644 venv/lib/python3.10/site-packages/attr/validators.py create mode 100644 venv/lib/python3.10/site-packages/attr/validators.pyi create mode 100644 venv/lib/python3.10/site-packages/attrs-25.4.0.dist-info/INSTALLER create mode 100644 venv/lib/python3.10/site-packages/attrs-25.4.0.dist-info/METADATA create mode 100644 venv/lib/python3.10/site-packages/attrs-25.4.0.dist-info/RECORD create mode 100644 venv/lib/python3.10/site-packages/attrs-25.4.0.dist-info/WHEEL create mode 100644 venv/lib/python3.10/site-packages/attrs-25.4.0.dist-info/licenses/LICENSE create mode 100644 venv/lib/python3.10/site-packages/attrs/__init__.py create mode 100644 venv/lib/python3.10/site-packages/attrs/__init__.pyi create mode 100644 venv/lib/python3.10/site-packages/attrs/__pycache__/__init__.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/attrs/__pycache__/converters.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/attrs/__pycache__/exceptions.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/attrs/__pycache__/filters.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/attrs/__pycache__/setters.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/attrs/__pycache__/validators.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/attrs/converters.py create mode 100644 venv/lib/python3.10/site-packages/attrs/exceptions.py create mode 100644 venv/lib/python3.10/site-packages/attrs/filters.py create mode 100644 venv/lib/python3.10/site-packages/attrs/py.typed create mode 100644 venv/lib/python3.10/site-packages/attrs/setters.py create mode 100644 venv/lib/python3.10/site-packages/attrs/validators.py create mode 100644 venv/lib/python3.10/site-packages/backports/asyncio/runner/__init__.py create mode 100644 venv/lib/python3.10/site-packages/backports/asyncio/runner/__pycache__/__init__.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/backports/asyncio/runner/__pycache__/_int_to_enum.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/backports/asyncio/runner/__pycache__/_patch.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/backports/asyncio/runner/__pycache__/runner.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/backports/asyncio/runner/__pycache__/tasks.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/backports/asyncio/runner/_int_to_enum.py create mode 100644 venv/lib/python3.10/site-packages/backports/asyncio/runner/_patch.py create mode 100644 venv/lib/python3.10/site-packages/backports/asyncio/runner/py.typed create mode 100644 venv/lib/python3.10/site-packages/backports/asyncio/runner/runner.py create mode 100644 venv/lib/python3.10/site-packages/backports/asyncio/runner/runner.pyi create mode 100644 venv/lib/python3.10/site-packages/backports/asyncio/runner/tasks.py create mode 100644 venv/lib/python3.10/site-packages/backports_asyncio_runner-1.2.0.dist-info/INSTALLER create mode 100644 venv/lib/python3.10/site-packages/backports_asyncio_runner-1.2.0.dist-info/METADATA create mode 100644 venv/lib/python3.10/site-packages/backports_asyncio_runner-1.2.0.dist-info/RECORD create mode 100644 venv/lib/python3.10/site-packages/backports_asyncio_runner-1.2.0.dist-info/WHEEL create mode 100644 venv/lib/python3.10/site-packages/backports_asyncio_runner-1.2.0.dist-info/licenses/LICENSE.md create mode 100644 venv/lib/python3.10/site-packages/certifi-2025.11.12.dist-info/INSTALLER create mode 100644 venv/lib/python3.10/site-packages/certifi-2025.11.12.dist-info/METADATA create mode 100644 venv/lib/python3.10/site-packages/certifi-2025.11.12.dist-info/RECORD create mode 100644 venv/lib/python3.10/site-packages/certifi-2025.11.12.dist-info/WHEEL create mode 100644 venv/lib/python3.10/site-packages/certifi-2025.11.12.dist-info/licenses/LICENSE create mode 100644 venv/lib/python3.10/site-packages/certifi-2025.11.12.dist-info/top_level.txt create mode 100644 venv/lib/python3.10/site-packages/certifi/__init__.py create mode 100644 venv/lib/python3.10/site-packages/certifi/__main__.py create mode 100644 venv/lib/python3.10/site-packages/certifi/__pycache__/__init__.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/certifi/__pycache__/__main__.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/certifi/__pycache__/core.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/certifi/cacert.pem create mode 100644 venv/lib/python3.10/site-packages/certifi/core.py create mode 100644 venv/lib/python3.10/site-packages/certifi/py.typed create mode 100644 venv/lib/python3.10/site-packages/curl_cffi-0.14.0.dist-info/INSTALLER create mode 100644 venv/lib/python3.10/site-packages/curl_cffi-0.14.0.dist-info/METADATA create mode 100644 venv/lib/python3.10/site-packages/curl_cffi-0.14.0.dist-info/RECORD create mode 100644 venv/lib/python3.10/site-packages/curl_cffi-0.14.0.dist-info/REQUESTED create mode 100644 venv/lib/python3.10/site-packages/curl_cffi-0.14.0.dist-info/WHEEL create mode 100644 venv/lib/python3.10/site-packages/curl_cffi-0.14.0.dist-info/entry_points.txt rename venv/lib/python3.10/site-packages/{curl_cffi-0.5.10.dist-info => curl_cffi-0.14.0.dist-info/licenses}/LICENSE (96%) rename venv/lib/python3.10/site-packages/{curl_cffi-0.5.10.dist-info => curl_cffi-0.14.0.dist-info}/top_level.txt (100%) delete mode 100644 venv/lib/python3.10/site-packages/curl_cffi-0.5.10.dist-info/METADATA delete mode 100644 venv/lib/python3.10/site-packages/curl_cffi-0.5.10.dist-info/RECORD delete mode 100644 venv/lib/python3.10/site-packages/curl_cffi-0.5.10.dist-info/WHEEL delete mode 100755 venv/lib/python3.10/site-packages/curl_cffi.libs/libcurl-impersonate-chrome-ad79e5fb.so.4.8.0 create mode 100644 venv/lib/python3.10/site-packages/curl_cffi/__pycache__/_asyncio_selector.cpython-310.pyc delete mode 100644 venv/lib/python3.10/site-packages/curl_cffi/__pycache__/build.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/curl_cffi/__pycache__/cli.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/curl_cffi/__pycache__/utils.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/curl_cffi/_asyncio_selector.py delete mode 100644 venv/lib/python3.10/site-packages/curl_cffi/build.py delete mode 100644 venv/lib/python3.10/site-packages/curl_cffi/cacert.pem create mode 100644 venv/lib/python3.10/site-packages/curl_cffi/cli.py delete mode 100644 venv/lib/python3.10/site-packages/curl_cffi/ffi/cdef.c delete mode 100644 venv/lib/python3.10/site-packages/curl_cffi/ffi/shim.c delete mode 100644 venv/lib/python3.10/site-packages/curl_cffi/ffi/shim.h delete mode 100644 venv/lib/python3.10/site-packages/curl_cffi/include/curl/Makefile.am delete mode 100644 venv/lib/python3.10/site-packages/curl_cffi/include/curl/Makefile.in delete mode 100644 venv/lib/python3.10/site-packages/curl_cffi/include/curl/curl.h delete mode 100644 venv/lib/python3.10/site-packages/curl_cffi/include/curl/curlver.h delete mode 100644 venv/lib/python3.10/site-packages/curl_cffi/include/curl/easy.h delete mode 100644 venv/lib/python3.10/site-packages/curl_cffi/include/curl/header.h delete mode 100644 venv/lib/python3.10/site-packages/curl_cffi/include/curl/mprintf.h delete mode 100644 venv/lib/python3.10/site-packages/curl_cffi/include/curl/multi.h delete mode 100644 venv/lib/python3.10/site-packages/curl_cffi/include/curl/options.h delete mode 100644 venv/lib/python3.10/site-packages/curl_cffi/include/curl/stdcheaders.h delete mode 100644 venv/lib/python3.10/site-packages/curl_cffi/include/curl/system.h delete mode 100644 venv/lib/python3.10/site-packages/curl_cffi/include/curl/typecheck-gcc.h delete mode 100644 venv/lib/python3.10/site-packages/curl_cffi/include/curl/urlapi.h create mode 100644 venv/lib/python3.10/site-packages/curl_cffi/py.typed create mode 100644 venv/lib/python3.10/site-packages/curl_cffi/requests/__pycache__/exceptions.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/curl_cffi/requests/__pycache__/impersonate.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/curl_cffi/requests/__pycache__/utils.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/curl_cffi/requests/__pycache__/websockets.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/curl_cffi/requests/exceptions.py create mode 100644 venv/lib/python3.10/site-packages/curl_cffi/requests/impersonate.py create mode 100644 venv/lib/python3.10/site-packages/curl_cffi/requests/utils.py create mode 100644 venv/lib/python3.10/site-packages/curl_cffi/requests/websockets.py create mode 100644 venv/lib/python3.10/site-packages/curl_cffi/utils.py create mode 100644 venv/lib/python3.10/site-packages/frozenlist-1.8.0.dist-info/INSTALLER create mode 100644 venv/lib/python3.10/site-packages/frozenlist-1.8.0.dist-info/METADATA create mode 100644 venv/lib/python3.10/site-packages/frozenlist-1.8.0.dist-info/RECORD create mode 100644 venv/lib/python3.10/site-packages/frozenlist-1.8.0.dist-info/WHEEL create mode 100644 venv/lib/python3.10/site-packages/frozenlist-1.8.0.dist-info/licenses/LICENSE create mode 100644 venv/lib/python3.10/site-packages/frozenlist-1.8.0.dist-info/top_level.txt create mode 100644 venv/lib/python3.10/site-packages/frozenlist/__init__.py create mode 100644 venv/lib/python3.10/site-packages/frozenlist/__init__.pyi create mode 100644 venv/lib/python3.10/site-packages/frozenlist/__pycache__/__init__.cpython-310.pyc create mode 100755 venv/lib/python3.10/site-packages/frozenlist/_frozenlist.cpython-310-x86_64-linux-gnu.so create mode 100644 venv/lib/python3.10/site-packages/frozenlist/_frozenlist.pyx create mode 100644 venv/lib/python3.10/site-packages/frozenlist/py.typed create mode 100644 venv/lib/python3.10/site-packages/idna-3.11.dist-info/INSTALLER create mode 100644 venv/lib/python3.10/site-packages/idna-3.11.dist-info/METADATA create mode 100644 venv/lib/python3.10/site-packages/idna-3.11.dist-info/RECORD create mode 100644 venv/lib/python3.10/site-packages/idna-3.11.dist-info/WHEEL create mode 100644 venv/lib/python3.10/site-packages/idna-3.11.dist-info/licenses/LICENSE.md create mode 100644 venv/lib/python3.10/site-packages/idna/__init__.py create mode 100644 venv/lib/python3.10/site-packages/idna/__pycache__/__init__.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/idna/__pycache__/codec.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/idna/__pycache__/compat.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/idna/__pycache__/core.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/idna/__pycache__/idnadata.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/idna/__pycache__/intranges.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/idna/__pycache__/package_data.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/idna/__pycache__/uts46data.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/idna/codec.py create mode 100644 venv/lib/python3.10/site-packages/idna/compat.py create mode 100644 venv/lib/python3.10/site-packages/idna/core.py create mode 100644 venv/lib/python3.10/site-packages/idna/idnadata.py create mode 100644 venv/lib/python3.10/site-packages/idna/intranges.py create mode 100644 venv/lib/python3.10/site-packages/idna/package_data.py create mode 100644 venv/lib/python3.10/site-packages/idna/py.typed create mode 100644 venv/lib/python3.10/site-packages/idna/uts46data.py create mode 100644 venv/lib/python3.10/site-packages/multidict-6.7.0.dist-info/INSTALLER create mode 100644 venv/lib/python3.10/site-packages/multidict-6.7.0.dist-info/METADATA create mode 100644 venv/lib/python3.10/site-packages/multidict-6.7.0.dist-info/RECORD create mode 100644 venv/lib/python3.10/site-packages/multidict-6.7.0.dist-info/WHEEL create mode 100644 venv/lib/python3.10/site-packages/multidict-6.7.0.dist-info/licenses/LICENSE create mode 100644 venv/lib/python3.10/site-packages/multidict-6.7.0.dist-info/top_level.txt create mode 100644 venv/lib/python3.10/site-packages/multidict/__init__.py create mode 100644 venv/lib/python3.10/site-packages/multidict/__pycache__/__init__.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/multidict/__pycache__/_abc.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/multidict/__pycache__/_compat.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/multidict/__pycache__/_multidict_py.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/multidict/_abc.py create mode 100644 venv/lib/python3.10/site-packages/multidict/_compat.py create mode 100755 venv/lib/python3.10/site-packages/multidict/_multidict.cpython-310-x86_64-linux-gnu.so create mode 100644 venv/lib/python3.10/site-packages/multidict/_multidict_py.py create mode 100644 venv/lib/python3.10/site-packages/multidict/py.typed create mode 100644 venv/lib/python3.10/site-packages/propcache-0.4.1.dist-info/INSTALLER create mode 100644 venv/lib/python3.10/site-packages/propcache-0.4.1.dist-info/METADATA create mode 100644 venv/lib/python3.10/site-packages/propcache-0.4.1.dist-info/RECORD create mode 100644 venv/lib/python3.10/site-packages/propcache-0.4.1.dist-info/WHEEL create mode 100644 venv/lib/python3.10/site-packages/propcache-0.4.1.dist-info/licenses/LICENSE create mode 100644 venv/lib/python3.10/site-packages/propcache-0.4.1.dist-info/licenses/NOTICE create mode 100644 venv/lib/python3.10/site-packages/propcache-0.4.1.dist-info/top_level.txt create mode 100644 venv/lib/python3.10/site-packages/propcache/__init__.py create mode 100644 venv/lib/python3.10/site-packages/propcache/__pycache__/__init__.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/propcache/__pycache__/_helpers.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/propcache/__pycache__/_helpers_py.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/propcache/__pycache__/api.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/propcache/_helpers.py create mode 100755 venv/lib/python3.10/site-packages/propcache/_helpers_c.cpython-310-x86_64-linux-gnu.so create mode 100644 venv/lib/python3.10/site-packages/propcache/_helpers_c.pyx create mode 100644 venv/lib/python3.10/site-packages/propcache/_helpers_py.py create mode 100644 venv/lib/python3.10/site-packages/propcache/api.py create mode 100644 venv/lib/python3.10/site-packages/propcache/py.typed create mode 100644 venv/lib/python3.10/site-packages/pygments-2.19.2.dist-info/INSTALLER create mode 100644 venv/lib/python3.10/site-packages/pygments-2.19.2.dist-info/METADATA create mode 100644 venv/lib/python3.10/site-packages/pygments-2.19.2.dist-info/RECORD create mode 100644 venv/lib/python3.10/site-packages/pygments-2.19.2.dist-info/WHEEL create mode 100644 venv/lib/python3.10/site-packages/pygments-2.19.2.dist-info/entry_points.txt create mode 100644 venv/lib/python3.10/site-packages/pygments-2.19.2.dist-info/licenses/AUTHORS create mode 100644 venv/lib/python3.10/site-packages/pygments-2.19.2.dist-info/licenses/LICENSE create mode 100644 venv/lib/python3.10/site-packages/pygments/__init__.py create mode 100644 venv/lib/python3.10/site-packages/pygments/__main__.py create mode 100644 venv/lib/python3.10/site-packages/pygments/__pycache__/__init__.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/__pycache__/__main__.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/__pycache__/cmdline.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/__pycache__/console.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/__pycache__/filter.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/__pycache__/formatter.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/__pycache__/lexer.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/__pycache__/modeline.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/__pycache__/plugin.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/__pycache__/regexopt.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/__pycache__/scanner.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/__pycache__/sphinxext.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/__pycache__/style.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/__pycache__/token.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/__pycache__/unistring.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/__pycache__/util.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/cmdline.py create mode 100644 venv/lib/python3.10/site-packages/pygments/console.py create mode 100644 venv/lib/python3.10/site-packages/pygments/filter.py create mode 100644 venv/lib/python3.10/site-packages/pygments/filters/__init__.py create mode 100644 venv/lib/python3.10/site-packages/pygments/filters/__pycache__/__init__.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/formatter.py create mode 100644 venv/lib/python3.10/site-packages/pygments/formatters/__init__.py create mode 100644 venv/lib/python3.10/site-packages/pygments/formatters/__pycache__/__init__.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/formatters/__pycache__/_mapping.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/formatters/__pycache__/bbcode.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/formatters/__pycache__/groff.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/formatters/__pycache__/html.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/formatters/__pycache__/img.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/formatters/__pycache__/irc.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/formatters/__pycache__/latex.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/formatters/__pycache__/other.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/formatters/__pycache__/pangomarkup.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/formatters/__pycache__/rtf.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/formatters/__pycache__/svg.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/formatters/__pycache__/terminal.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/formatters/__pycache__/terminal256.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/formatters/_mapping.py create mode 100644 venv/lib/python3.10/site-packages/pygments/formatters/bbcode.py create mode 100644 venv/lib/python3.10/site-packages/pygments/formatters/groff.py create mode 100644 venv/lib/python3.10/site-packages/pygments/formatters/html.py create mode 100644 venv/lib/python3.10/site-packages/pygments/formatters/img.py create mode 100644 venv/lib/python3.10/site-packages/pygments/formatters/irc.py create mode 100644 venv/lib/python3.10/site-packages/pygments/formatters/latex.py create mode 100644 venv/lib/python3.10/site-packages/pygments/formatters/other.py create mode 100644 venv/lib/python3.10/site-packages/pygments/formatters/pangomarkup.py create mode 100644 venv/lib/python3.10/site-packages/pygments/formatters/rtf.py create mode 100644 venv/lib/python3.10/site-packages/pygments/formatters/svg.py create mode 100644 venv/lib/python3.10/site-packages/pygments/formatters/terminal.py create mode 100644 venv/lib/python3.10/site-packages/pygments/formatters/terminal256.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexer.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__init__.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/__init__.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/_ada_builtins.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/_asy_builtins.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/_cl_builtins.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/_cocoa_builtins.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/_csound_builtins.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/_css_builtins.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/_googlesql_builtins.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/_julia_builtins.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/_lasso_builtins.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/_lilypond_builtins.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/_lua_builtins.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/_luau_builtins.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/_mapping.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/_mql_builtins.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/_mysql_builtins.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/_openedge_builtins.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/_php_builtins.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/_postgres_builtins.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/_qlik_builtins.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/_scheme_builtins.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/_scilab_builtins.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/_sourcemod_builtins.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/_sql_builtins.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/_stan_builtins.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/_stata_builtins.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/_tsql_builtins.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/_usd_builtins.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/_vbscript_builtins.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/_vim_builtins.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/actionscript.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/ada.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/agile.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/algebra.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/ambient.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/amdgpu.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/ampl.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/apdlexer.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/apl.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/archetype.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/arrow.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/arturo.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/asc.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/asm.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/asn1.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/automation.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/bare.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/basic.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/bdd.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/berry.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/bibtex.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/blueprint.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/boa.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/bqn.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/business.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/c_cpp.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/c_like.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/capnproto.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/carbon.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/cddl.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/chapel.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/clean.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/codeql.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/comal.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/compiled.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/configs.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/console.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/cplint.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/crystal.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/csound.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/css.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/d.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/dalvik.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/data.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/dax.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/devicetree.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/diff.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/dns.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/dotnet.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/dsls.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/dylan.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/ecl.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/eiffel.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/elm.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/elpi.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/email.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/erlang.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/esoteric.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/ezhil.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/factor.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/fantom.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/felix.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/fift.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/floscript.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/forth.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/fortran.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/foxpro.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/freefem.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/func.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/functional.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/futhark.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/gcodelexer.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/gdscript.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/gleam.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/go.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/grammar_notation.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/graph.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/graphics.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/graphql.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/graphviz.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/gsql.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/hare.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/haskell.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/haxe.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/hdl.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/hexdump.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/html.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/idl.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/igor.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/inferno.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/installers.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/int_fiction.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/iolang.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/j.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/javascript.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/jmespath.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/jslt.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/json5.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/jsonnet.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/jsx.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/julia.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/jvm.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/kuin.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/kusto.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/ldap.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/lean.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/lilypond.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/lisp.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/macaulay2.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/make.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/maple.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/markup.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/math.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/matlab.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/maxima.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/meson.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/mime.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/minecraft.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/mips.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/ml.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/modeling.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/modula2.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/mojo.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/monte.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/mosel.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/ncl.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/nimrod.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/nit.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/nix.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/numbair.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/oberon.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/objective.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/ooc.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/openscad.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/other.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/parasail.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/parsers.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/pascal.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/pawn.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/pddl.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/perl.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/phix.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/php.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/pointless.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/pony.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/praat.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/procfile.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/prolog.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/promql.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/prql.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/ptx.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/python.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/q.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/qlik.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/qvt.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/r.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/rdf.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/rebol.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/rego.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/resource.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/ride.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/rita.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/rnc.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/roboconf.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/robotframework.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/ruby.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/rust.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/sas.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/savi.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/scdoc.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/scripting.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/sgf.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/shell.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/sieve.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/slash.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/smalltalk.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/smithy.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/smv.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/snobol.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/solidity.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/soong.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/sophia.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/special.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/spice.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/sql.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/srcinfo.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/stata.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/supercollider.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/tablegen.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/tact.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/tal.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/tcl.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/teal.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/templates.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/teraterm.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/testing.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/text.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/textedit.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/textfmts.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/theorem.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/thingsdb.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/tlb.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/tls.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/tnt.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/trafficscript.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/typoscript.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/typst.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/ul4.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/unicon.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/urbi.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/usd.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/varnish.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/verification.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/verifpal.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/vip.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/vyper.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/web.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/webassembly.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/webidl.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/webmisc.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/wgsl.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/whiley.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/wowtoc.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/wren.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/x10.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/xorg.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/yang.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/yara.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/__pycache__/zig.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/_ada_builtins.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/_asy_builtins.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/_cl_builtins.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/_cocoa_builtins.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/_csound_builtins.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/_css_builtins.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/_googlesql_builtins.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/_julia_builtins.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/_lasso_builtins.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/_lilypond_builtins.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/_lua_builtins.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/_luau_builtins.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/_mapping.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/_mql_builtins.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/_mysql_builtins.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/_openedge_builtins.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/_php_builtins.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/_postgres_builtins.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/_qlik_builtins.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/_scheme_builtins.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/_scilab_builtins.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/_sourcemod_builtins.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/_sql_builtins.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/_stan_builtins.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/_stata_builtins.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/_tsql_builtins.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/_usd_builtins.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/_vbscript_builtins.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/_vim_builtins.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/actionscript.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/ada.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/agile.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/algebra.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/ambient.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/amdgpu.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/ampl.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/apdlexer.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/apl.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/archetype.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/arrow.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/arturo.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/asc.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/asm.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/asn1.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/automation.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/bare.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/basic.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/bdd.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/berry.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/bibtex.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/blueprint.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/boa.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/bqn.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/business.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/c_cpp.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/c_like.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/capnproto.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/carbon.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/cddl.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/chapel.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/clean.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/codeql.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/comal.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/compiled.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/configs.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/console.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/cplint.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/crystal.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/csound.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/css.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/d.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/dalvik.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/data.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/dax.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/devicetree.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/diff.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/dns.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/dotnet.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/dsls.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/dylan.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/ecl.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/eiffel.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/elm.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/elpi.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/email.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/erlang.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/esoteric.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/ezhil.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/factor.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/fantom.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/felix.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/fift.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/floscript.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/forth.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/fortran.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/foxpro.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/freefem.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/func.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/functional.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/futhark.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/gcodelexer.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/gdscript.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/gleam.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/go.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/grammar_notation.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/graph.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/graphics.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/graphql.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/graphviz.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/gsql.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/hare.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/haskell.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/haxe.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/hdl.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/hexdump.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/html.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/idl.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/igor.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/inferno.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/installers.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/int_fiction.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/iolang.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/j.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/javascript.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/jmespath.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/jslt.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/json5.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/jsonnet.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/jsx.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/julia.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/jvm.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/kuin.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/kusto.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/ldap.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/lean.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/lilypond.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/lisp.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/macaulay2.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/make.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/maple.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/markup.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/math.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/matlab.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/maxima.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/meson.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/mime.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/minecraft.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/mips.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/ml.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/modeling.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/modula2.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/mojo.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/monte.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/mosel.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/ncl.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/nimrod.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/nit.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/nix.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/numbair.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/oberon.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/objective.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/ooc.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/openscad.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/other.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/parasail.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/parsers.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/pascal.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/pawn.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/pddl.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/perl.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/phix.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/php.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/pointless.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/pony.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/praat.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/procfile.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/prolog.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/promql.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/prql.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/ptx.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/python.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/q.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/qlik.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/qvt.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/r.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/rdf.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/rebol.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/rego.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/resource.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/ride.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/rita.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/rnc.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/roboconf.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/robotframework.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/ruby.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/rust.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/sas.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/savi.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/scdoc.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/scripting.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/sgf.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/shell.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/sieve.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/slash.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/smalltalk.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/smithy.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/smv.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/snobol.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/solidity.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/soong.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/sophia.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/special.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/spice.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/sql.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/srcinfo.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/stata.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/supercollider.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/tablegen.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/tact.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/tal.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/tcl.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/teal.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/templates.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/teraterm.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/testing.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/text.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/textedit.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/textfmts.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/theorem.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/thingsdb.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/tlb.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/tls.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/tnt.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/trafficscript.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/typoscript.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/typst.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/ul4.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/unicon.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/urbi.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/usd.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/varnish.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/verification.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/verifpal.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/vip.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/vyper.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/web.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/webassembly.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/webidl.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/webmisc.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/wgsl.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/whiley.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/wowtoc.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/wren.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/x10.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/xorg.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/yang.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/yara.py create mode 100644 venv/lib/python3.10/site-packages/pygments/lexers/zig.py create mode 100644 venv/lib/python3.10/site-packages/pygments/modeline.py create mode 100644 venv/lib/python3.10/site-packages/pygments/plugin.py create mode 100644 venv/lib/python3.10/site-packages/pygments/regexopt.py create mode 100644 venv/lib/python3.10/site-packages/pygments/scanner.py create mode 100644 venv/lib/python3.10/site-packages/pygments/sphinxext.py create mode 100644 venv/lib/python3.10/site-packages/pygments/style.py create mode 100644 venv/lib/python3.10/site-packages/pygments/styles/__init__.py create mode 100644 venv/lib/python3.10/site-packages/pygments/styles/__pycache__/__init__.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/styles/__pycache__/_mapping.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/styles/__pycache__/abap.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/styles/__pycache__/algol.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/styles/__pycache__/algol_nu.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/styles/__pycache__/arduino.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/styles/__pycache__/autumn.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/styles/__pycache__/borland.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/styles/__pycache__/bw.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/styles/__pycache__/coffee.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/styles/__pycache__/colorful.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/styles/__pycache__/default.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/styles/__pycache__/dracula.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/styles/__pycache__/emacs.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/styles/__pycache__/friendly.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/styles/__pycache__/friendly_grayscale.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/styles/__pycache__/fruity.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/styles/__pycache__/gh_dark.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/styles/__pycache__/gruvbox.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/styles/__pycache__/igor.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/styles/__pycache__/inkpot.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/styles/__pycache__/lightbulb.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/styles/__pycache__/lilypond.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/styles/__pycache__/lovelace.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/styles/__pycache__/manni.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/styles/__pycache__/material.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/styles/__pycache__/monokai.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/styles/__pycache__/murphy.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/styles/__pycache__/native.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/styles/__pycache__/nord.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/styles/__pycache__/onedark.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/styles/__pycache__/paraiso_dark.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/styles/__pycache__/paraiso_light.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/styles/__pycache__/pastie.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/styles/__pycache__/perldoc.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/styles/__pycache__/rainbow_dash.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/styles/__pycache__/rrt.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/styles/__pycache__/sas.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/styles/__pycache__/solarized.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/styles/__pycache__/staroffice.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/styles/__pycache__/stata_dark.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/styles/__pycache__/stata_light.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/styles/__pycache__/tango.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/styles/__pycache__/trac.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/styles/__pycache__/vim.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/styles/__pycache__/vs.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/styles/__pycache__/xcode.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/styles/__pycache__/zenburn.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pygments/styles/_mapping.py create mode 100644 venv/lib/python3.10/site-packages/pygments/styles/abap.py create mode 100644 venv/lib/python3.10/site-packages/pygments/styles/algol.py create mode 100644 venv/lib/python3.10/site-packages/pygments/styles/algol_nu.py create mode 100644 venv/lib/python3.10/site-packages/pygments/styles/arduino.py create mode 100644 venv/lib/python3.10/site-packages/pygments/styles/autumn.py create mode 100644 venv/lib/python3.10/site-packages/pygments/styles/borland.py create mode 100644 venv/lib/python3.10/site-packages/pygments/styles/bw.py create mode 100644 venv/lib/python3.10/site-packages/pygments/styles/coffee.py create mode 100644 venv/lib/python3.10/site-packages/pygments/styles/colorful.py create mode 100644 venv/lib/python3.10/site-packages/pygments/styles/default.py create mode 100644 venv/lib/python3.10/site-packages/pygments/styles/dracula.py create mode 100644 venv/lib/python3.10/site-packages/pygments/styles/emacs.py create mode 100644 venv/lib/python3.10/site-packages/pygments/styles/friendly.py create mode 100644 venv/lib/python3.10/site-packages/pygments/styles/friendly_grayscale.py create mode 100644 venv/lib/python3.10/site-packages/pygments/styles/fruity.py create mode 100644 venv/lib/python3.10/site-packages/pygments/styles/gh_dark.py create mode 100644 venv/lib/python3.10/site-packages/pygments/styles/gruvbox.py create mode 100644 venv/lib/python3.10/site-packages/pygments/styles/igor.py create mode 100644 venv/lib/python3.10/site-packages/pygments/styles/inkpot.py create mode 100644 venv/lib/python3.10/site-packages/pygments/styles/lightbulb.py create mode 100644 venv/lib/python3.10/site-packages/pygments/styles/lilypond.py create mode 100644 venv/lib/python3.10/site-packages/pygments/styles/lovelace.py create mode 100644 venv/lib/python3.10/site-packages/pygments/styles/manni.py create mode 100644 venv/lib/python3.10/site-packages/pygments/styles/material.py create mode 100644 venv/lib/python3.10/site-packages/pygments/styles/monokai.py create mode 100644 venv/lib/python3.10/site-packages/pygments/styles/murphy.py create mode 100644 venv/lib/python3.10/site-packages/pygments/styles/native.py create mode 100644 venv/lib/python3.10/site-packages/pygments/styles/nord.py create mode 100644 venv/lib/python3.10/site-packages/pygments/styles/onedark.py create mode 100644 venv/lib/python3.10/site-packages/pygments/styles/paraiso_dark.py create mode 100644 venv/lib/python3.10/site-packages/pygments/styles/paraiso_light.py create mode 100644 venv/lib/python3.10/site-packages/pygments/styles/pastie.py create mode 100644 venv/lib/python3.10/site-packages/pygments/styles/perldoc.py create mode 100644 venv/lib/python3.10/site-packages/pygments/styles/rainbow_dash.py create mode 100644 venv/lib/python3.10/site-packages/pygments/styles/rrt.py create mode 100644 venv/lib/python3.10/site-packages/pygments/styles/sas.py create mode 100644 venv/lib/python3.10/site-packages/pygments/styles/solarized.py create mode 100644 venv/lib/python3.10/site-packages/pygments/styles/staroffice.py create mode 100644 venv/lib/python3.10/site-packages/pygments/styles/stata_dark.py create mode 100644 venv/lib/python3.10/site-packages/pygments/styles/stata_light.py create mode 100644 venv/lib/python3.10/site-packages/pygments/styles/tango.py create mode 100644 venv/lib/python3.10/site-packages/pygments/styles/trac.py create mode 100644 venv/lib/python3.10/site-packages/pygments/styles/vim.py create mode 100644 venv/lib/python3.10/site-packages/pygments/styles/vs.py create mode 100644 venv/lib/python3.10/site-packages/pygments/styles/xcode.py create mode 100644 venv/lib/python3.10/site-packages/pygments/styles/zenburn.py create mode 100644 venv/lib/python3.10/site-packages/pygments/token.py create mode 100644 venv/lib/python3.10/site-packages/pygments/unistring.py create mode 100644 venv/lib/python3.10/site-packages/pygments/util.py delete mode 100644 venv/lib/python3.10/site-packages/pytest-7.4.3.dist-info/RECORD create mode 100644 venv/lib/python3.10/site-packages/pytest-9.0.2.dist-info/INSTALLER rename venv/lib/python3.10/site-packages/{pytest-7.4.3.dist-info => pytest-9.0.2.dist-info}/METADATA (73%) create mode 100644 venv/lib/python3.10/site-packages/pytest-9.0.2.dist-info/RECORD create mode 100644 venv/lib/python3.10/site-packages/pytest-9.0.2.dist-info/WHEEL rename venv/lib/python3.10/site-packages/{pytest-7.4.3.dist-info => pytest-9.0.2.dist-info}/entry_points.txt (100%) rename venv/lib/python3.10/site-packages/{pytest-7.4.3.dist-info => pytest-9.0.2.dist-info/licenses}/LICENSE (100%) rename venv/lib/python3.10/site-packages/{pytest-7.4.3.dist-info => pytest-9.0.2.dist-info}/top_level.txt (100%) create mode 100644 venv/lib/python3.10/site-packages/pytest_asyncio-1.3.0.dist-info/INSTALLER create mode 100644 venv/lib/python3.10/site-packages/pytest_asyncio-1.3.0.dist-info/METADATA create mode 100644 venv/lib/python3.10/site-packages/pytest_asyncio-1.3.0.dist-info/RECORD create mode 100644 venv/lib/python3.10/site-packages/pytest_asyncio-1.3.0.dist-info/REQUESTED create mode 100644 venv/lib/python3.10/site-packages/pytest_asyncio-1.3.0.dist-info/WHEEL create mode 100644 venv/lib/python3.10/site-packages/pytest_asyncio-1.3.0.dist-info/entry_points.txt create mode 100644 venv/lib/python3.10/site-packages/pytest_asyncio-1.3.0.dist-info/licenses/LICENSE create mode 100644 venv/lib/python3.10/site-packages/pytest_asyncio-1.3.0.dist-info/top_level.txt create mode 100644 venv/lib/python3.10/site-packages/pytest_asyncio/__init__.py create mode 100644 venv/lib/python3.10/site-packages/pytest_asyncio/__pycache__/__init__.cpython-310-pytest-9.0.2.pyc create mode 100644 venv/lib/python3.10/site-packages/pytest_asyncio/__pycache__/__init__.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pytest_asyncio/__pycache__/plugin.cpython-310-pytest-9.0.2.pyc create mode 100644 venv/lib/python3.10/site-packages/pytest_asyncio/__pycache__/plugin.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pytest_asyncio/plugin.py create mode 100644 venv/lib/python3.10/site-packages/pytest_asyncio/py.typed create mode 100644 venv/lib/python3.10/site-packages/yarl-1.22.0.dist-info/INSTALLER create mode 100644 venv/lib/python3.10/site-packages/yarl-1.22.0.dist-info/METADATA create mode 100644 venv/lib/python3.10/site-packages/yarl-1.22.0.dist-info/RECORD create mode 100644 venv/lib/python3.10/site-packages/yarl-1.22.0.dist-info/WHEEL create mode 100644 venv/lib/python3.10/site-packages/yarl-1.22.0.dist-info/licenses/LICENSE create mode 100644 venv/lib/python3.10/site-packages/yarl-1.22.0.dist-info/licenses/NOTICE create mode 100644 venv/lib/python3.10/site-packages/yarl-1.22.0.dist-info/top_level.txt create mode 100644 venv/lib/python3.10/site-packages/yarl/__init__.py create mode 100644 venv/lib/python3.10/site-packages/yarl/__pycache__/__init__.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/yarl/__pycache__/_parse.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/yarl/__pycache__/_path.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/yarl/__pycache__/_query.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/yarl/__pycache__/_quoters.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/yarl/__pycache__/_quoting.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/yarl/__pycache__/_quoting_py.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/yarl/__pycache__/_url.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/yarl/_parse.py create mode 100644 venv/lib/python3.10/site-packages/yarl/_path.py create mode 100644 venv/lib/python3.10/site-packages/yarl/_query.py create mode 100644 venv/lib/python3.10/site-packages/yarl/_quoters.py create mode 100644 venv/lib/python3.10/site-packages/yarl/_quoting.py create mode 100755 venv/lib/python3.10/site-packages/yarl/_quoting_c.cpython-310-x86_64-linux-gnu.so create mode 100644 venv/lib/python3.10/site-packages/yarl/_quoting_c.pyx create mode 100644 venv/lib/python3.10/site-packages/yarl/_quoting_py.py create mode 100644 venv/lib/python3.10/site-packages/yarl/_url.py create mode 100644 venv/lib/python3.10/site-packages/yarl/py.typed diff --git a/requirements.txt b/requirements.txt index be0224e..d5035df 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,8 @@ redis==5.0.1 msgpack==1.0.7 -curl_cffi==0.5.10 +curl_cffi>=0.6.0 playwright==1.40.0 pydantic==2.5.3 -pytest==7.4.3 +pytest>=8.0.0 +pytest-asyncio>=0.23.0 +aiohttp==3.9.1 diff --git a/src/browser/__pycache__/__init__.cpython-310.pyc b/src/browser/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bf6e921d15000b767ec18fda140528ac2c9535c4 GIT binary patch literal 147 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 R&C%!e3wl+bhUaR=e*qu0FO2{I literal 0 HcmV?d00001 diff --git a/src/extractor/__pycache__/__init__.cpython-310.pyc b/src/extractor/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..052099bc7c29863fb07d88524f524cc66c736ad0 GIT binary patch literal 149 zcmd1j<>g`kf*XH5GC}lX5P=LBfgA@QE@lA|DGb33nv8xc8Hzx{2;!H8enx(7s(yB2 zajtG@acYr%d45rLaY15os=k|}tD}B#QL=t&MM+U&a!G!XetdjpUS>&ryk0@&Ee@O9 S{FKt1R6CG~#Y{kgg#iFfLLx-~ literal 0 HcmV?d00001 diff --git a/src/extractor/__pycache__/client.cpython-310.pyc b/src/extractor/__pycache__/client.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..34dc80540180f1bdf5e35848299dac9d70021a5e GIT binary patch literal 2529 zcmaJ?&2HO95ayCAilSuMQT_w8AbKdE#Fh#a=ugtLFXI=B^|v6e;gg zwnI38(*(#V=N{4k_AyV;2k2YuwWq#9(4w7LN|EG4SJ=_)?r`_p+4*MDr6t#Z_WSt< z(XXKWiG%rO!Qc*b`2z@MFcKR>(jf$OGd71-$I`kL(+=fyP=anV`Wh@aJt=eNlyoZh z3|3f9iv(P3-&XbMAg>i(c^snoe4{Y9 z16@7>!Ho`KM#p5NV=?QA>aKV>FCH>1BQHJ=)swx?sY^NJL5I1qt)|JYZaSBawEan0skgTkeYV zK*F>voKx(imL@}AqK85gu0L(4W8Z)-S3n$*Bg2QM0tx=vG2W5wrYUS#QRT@tO;b_H z5#Xd1ai1G1_znIsDZ=*T=Ba2G7jKQ8D#!Pu zB+7ihjtKCHY~qsq{LGK7sYOU&b=puz;D11u7eG7*;3vkZ@c_WzHwau{#}H*$fPirE zh$x7ooWCQY0+SkaA*wJ`uI~r%W?cAw1I7|BY#!GMCHch#C{wEtp-vm>{|g2^IR#S+ z<5cn{)i{*H4?{nDI^wE~+I|o#TTkttg;+s|hv=$HaGZ9PdE6lRg){iHiLwRH#v<8X za2jI7+iRyCgYukY#tC8M6v&sEN5;S!P-ecy7%1tH{k`$?e(k~ZrO{^2Gp!cHK{Xlz6J?qJ4HQj+ zAy*~v_?Ro3rNbaf6deUw|2=8L^IQi1h$|?rqPPYkf0aQDxBJ;JUJXX0I0^&gar+Qw zZXBMS9mcozZhzMLdNmpbJ>CxXqwYjK;Jc&g>?rB2UT@y;qwiW8bv?mhFefN4yy) zSmD~4bKp)lz3paI*>T!~w4-R0K>ot;DN@057E6Eb1%v}jsvM^2lZZ=YNuDXI2g1c% z;KR{?s$AqO3HbzrP}zm~nK&0qu!bI&AA>NDUAjVRmP2iVKbJUv+f|d8#Qo<=d4Zd= z`z*x#LZa|sOH334L)1~8kXgb&j?Q>$>MIFMi2_s8S(LbN3%bOco5wb}K<>IR$s`t7TA}dYYuLKQs9wJ20VO0ej%B80u||qyfK*d)et0ZUdj}{N4vXZ65M)oN+Jk?rlEu1m7ETnN5lfmX+Cib2cT}9F;B=Wz0*` zY(7@<%3VI8U~aWqt?m5WLoP>YBDsh4Wh`%c&2kYCwTI&*i-vqth*XG=;IaZAReS`;>MKIfL z*09zf=Y1d3{V-+Y81 ztBJt(#SqsO)iEfkQwM&o<$iLB>Q&%YBm7YCCvPJ)!_*4Y%2t>P-kKC#jYXA_i;&e+ zbvKYvSY%5@A8g8mKOI&Ns#7zeR3HykpI%5r@<*7aaK? D4itlq literal 0 HcmV?d00001 diff --git a/src/extractor/client.py b/src/extractor/client.py index 84ca88e..d4cee4a 100644 --- a/src/extractor/client.py +++ b/src/extractor/client.py @@ -58,7 +58,7 @@ class CurlClient: async def close(self) -> None: if self.session: - self.session.close() + await self.session.close() async def fetch(self, url: str) -> str: """ diff --git a/tests/__pycache__/__init__.cpython-310.pyc b/tests/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..873ac0c66345d4a24b656f00c542f7a6768f4789 GIT binary patch literal 141 zcmd1j<>g`kf*XH5GC}lX5P=LBfgA@QE@lA|DGb33nv8xc8Hzx{2;!H4enx(7s(yB2 zajtG@acYr%d45rLaY15os=k|}tD}BNYH>-ietdjpUS>&ryk0@&Ee@O9{FKt1R6CGK K#Y{kgg#iF0=N}ya literal 0 HcmV?d00001 diff --git a/tests/e2e/__pycache__/test_handover.cpython-310-pytest-7.4.3.pyc b/tests/e2e/__pycache__/test_handover.cpython-310-pytest-7.4.3.pyc new file mode 100644 index 0000000000000000000000000000000000000000..aaf59bb960bbe1f2fb9afd4f8e687336e90946ab GIT binary patch literal 3190 zcmb_e&5zs06`vs~ilV-y-84yymNk%KSu|Q}cWuW;x5&nDksM+Kb<&`^5M0gdin|oa z&WyZXl>(fsivm3v=&f-8=};8C6uqUVUix3mwdle91A1_Qv~Px5S=$CVmBb9^eay_8 z-+O%X(t6!R@VwK0FKRgm{Y??;j|s%v(D)Y%E}R$Lp`2X%~&(V#&}#D0PXO;RHcsXW1h7A>E&Pi*3n>JxN< z2OWYRAiw?`c1QPpEX^}|2%qNNaGXw#(ns%yNq9^du-v;77T=8{nusA*>dVJ~ZwcC8 zq4DoQ_X!p#M<@6K&#*A&W{wvaBVo-;1m~C-&y0oI)0}?a_279w9iDnLAz2zF!V{xV zcyW5ny%7xwWxS`Aq>-NbhP1f|#e~c9Qs~>#BGFK=GVCi`j1Fn^kdk02mUftBuryOP z%K8aUlYmj4r3t67TA9(GPbe>D{{gW&XuY_-Jxa%P`!wX^Hz%C3?X#4f@+=(E?H?c9 zJJ=R5fp60tsz<>nOi22WvR-yB8;Xc2Y@wG`j_2TWWIT2)7q>8L0`dImcM!S)pat9& zfFA&%0G#6mngLuV03gA_S{MK!nwJ!qGb1+bLXWj zHx$(OU*K(7iPK>ik5VrDN`XlAAj{%t7>X!Oq|MTapiGuSLV{x|w6(GVDnFSd2?$D@ zib*ERAKv}oK_n>9B4on1q?M&iNE5JzMG9SugN6pQ=o)Y=+13++F*rN`WC67fuz3qQ zox36oAEQMsj!3Z!bY0T72>toQfkcBSHOLl-sAqteltmZ$x?S5IZj)VNzE7BV%C^M^xtS0zE{02=c117nqG; zR8yk_s~>cwA+!cq$j-F}j6kEJ#(8Oh^%rIl_nGwynmMFOYPl0(7*|9cq>a3?0Lwnb z>LjxgwDJsWCH2?P%oWYty1*+uy0dCtowwHU_*w-1@|6fQ))9D$`6gKLbr}2|{cH=QZFofz!&Hc?&r0+|JwJ`A*(}bN>!(Rp;)2?>kDOec!$GENE!&=k5YOUHV^* z!Mj~`%DlexZez{M8*8&aRjA}OJ^NRB28?0$h8{PUv*1*kZbOW0K64;0y5tq|jl8R4 zWMdg48*4FAhZt!jSIEG!lA!PoD3z`hi3$5tS z*ASYvM?z$GwzpRa@(yc5s=5DM-E%&EVeIewczXSft?Yc)=k70-18?6`qw7B3Qs2JM zeRt;V7u=gZmu5^8R#9tM(ga1-^;Ng4x|`ER$iX(DS9we6XXkG*wdZqnVNGk7pnY_3 z$D6t@&oOPhL`2EJxL+y@V9&xh7%Yw3xn#lMrM*4K@9s=ny?4dxK9I!{YP6ithZ8QD zFj_Lf-nCWwusj@IHV+FwuWsP>(xoZ8WcdyQ%#k|mj_UT<8$dEuIlxVF-oLK<9o_Fu zn`;pD6{bq%roW*t2#u{Q4PzQI23G=-P7*$hjv;pB=5k}WX*h@bEzlvwzN@s{x3hC~ zj|U->?Eo&t81C$h4byQJGAe5z4h|<#ETV)Gp|H?B~({-;jRzPYTd0;sjV2^R4mKxaI*oElUJ zj$s)sqXk9P)|Y>@9NaNGc;^F8Ye5kM zy~23#OOMerrV5+Nils_K&11W2PC4Y~$uLT#UCaeWl&aiOlv7>KhP|bedgG#SF2Zs{ zNmc;&hPsN@aiBw3ms&car8N%OskVF#6;+rrT|zlO9H(Rw(;ulU!8f40h8$eeuVp%h J>)4eB{to~ubqN3f literal 0 HcmV?d00001 diff --git a/tests/e2e/__pycache__/test_handover.cpython-310-pytest-9.0.2.pyc b/tests/e2e/__pycache__/test_handover.cpython-310-pytest-9.0.2.pyc new file mode 100644 index 0000000000000000000000000000000000000000..aaf59bb960bbe1f2fb9afd4f8e687336e90946ab GIT binary patch literal 3190 zcmb_e&5zs06`vs~ilV-y-84yymNk%KSu|Q}cWuW;x5&nDksM+Kb<&`^5M0gdin|oa z&WyZXl>(fsivm3v=&f-8=};8C6uqUVUix3mwdle91A1_Qv~Px5S=$CVmBb9^eay_8 z-+O%X(t6!R@VwK0FKRgm{Y??;j|s%v(D)Y%E}R$Lp`2X%~&(V#&}#D0PXO;RHcsXW1h7A>E&Pi*3n>JxN< z2OWYRAiw?`c1QPpEX^}|2%qNNaGXw#(ns%yNq9^du-v;77T=8{nusA*>dVJ~ZwcC8 zq4DoQ_X!p#M<@6K&#*A&W{wvaBVo-;1m~C-&y0oI)0}?a_279w9iDnLAz2zF!V{xV zcyW5ny%7xwWxS`Aq>-NbhP1f|#e~c9Qs~>#BGFK=GVCi`j1Fn^kdk02mUftBuryOP z%K8aUlYmj4r3t67TA9(GPbe>D{{gW&XuY_-Jxa%P`!wX^Hz%C3?X#4f@+=(E?H?c9 zJJ=R5fp60tsz<>nOi22WvR-yB8;Xc2Y@wG`j_2TWWIT2)7q>8L0`dImcM!S)pat9& zfFA&%0G#6mngLuV03gA_S{MK!nwJ!qGb1+bLXWj zHx$(OU*K(7iPK>ik5VrDN`XlAAj{%t7>X!Oq|MTapiGuSLV{x|w6(GVDnFSd2?$D@ zib*ERAKv}oK_n>9B4on1q?M&iNE5JzMG9SugN6pQ=o)Y=+13++F*rN`WC67fuz3qQ zox36oAEQMsj!3Z!bY0T72>toQfkcBSHOLl-sAqteltmZ$x?S5IZj)VNzE7BV%C^M^xtS0zE{02=c117nqG; zR8yk_s~>cwA+!cq$j-F}j6kEJ#(8Oh^%rIl_nGwynmMFOYPl0(7*|9cq>a3?0Lwnb z>LjxgwDJsWCH2?P%oWYty1*+uy0dCtowwHU_*w-1@|6fQ))9D$`6gKLbr}2|{cH=QZFofz!&Hc?&r0+|JwJ`A*(}bN>!(Rp;)2?>kDOec!$GENE!&=k5YOUHV^* z!Mj~`%DlexZez{M8*8&aRjA}OJ^NRB28?0$h8{PUv*1*kZbOW0K64;0y5tq|jl8R4 zWMdg48*4FAhZt!jSIEG!lA!PoD3z`hi3$5tS z*ASYvM?z$GwzpRa@(yc5s=5DM-E%&EVeIewczXSft?Yc)=k70-18?6`qw7B3Qs2JM zeRt;V7u=gZmu5^8R#9tM(ga1-^;Ng4x|`ER$iX(DS9we6XXkG*wdZqnVNGk7pnY_3 z$D6t@&oOPhL`2EJxL+y@V9&xh7%Yw3xn#lMrM*4K@9s=ny?4dxK9I!{YP6ithZ8QD zFj_Lf-nCWwusj@IHV+FwuWsP>(xoZ8WcdyQ%#k|mj_UT<8$dEuIlxVF-oLK<9o_Fu zn`;pD6{bq%roW*t2#u{Q4PzQI23G=-P7*$hjv;pB=5k}WX*h@bEzlvwzN@s{x3hC~ zj|U->?Eo&t81C$h4byQJGAe5z4h|<#ETV)Gp|H?B~({-;jRzPYTd0;sjV2^R4mKxaI*oElUJ zj$s)sqXk9P)|Y>@9NaNGc;^F8Ye5kM zy~23#OOMerrV5+Nils_K&11W2PC4Y~$uLT#UCaeWl&aiOlv7>KhP|bedgG#SF2Zs{ zNmc;&hPsN@aiBw3ms&car8N%OskVF#6;+rrT|zlO9H(Rw(;ulU!8f40h8$eeuVp%h J>)4eB{to~ubqN3f literal 0 HcmV?d00001 diff --git a/venv/bin/curl-cffi b/venv/bin/curl-cffi new file mode 100755 index 0000000..7e0863b --- /dev/null +++ b/venv/bin/curl-cffi @@ -0,0 +1,8 @@ +#!/home/kasm-user/workspace/FAEA/venv/bin/python3 +# -*- coding: utf-8 -*- +import re +import sys +from curl_cffi.cli import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/venv/bin/pygmentize b/venv/bin/pygmentize new file mode 100755 index 0000000..496a6a8 --- /dev/null +++ b/venv/bin/pygmentize @@ -0,0 +1,8 @@ +#!/home/kasm-user/workspace/FAEA/venv/bin/python3 +# -*- coding: utf-8 -*- +import re +import sys +from pygments.cmdline import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/venv/lib/python3.10/site-packages/__pycache__/py.cpython-310.pyc b/venv/lib/python3.10/site-packages/__pycache__/py.cpython-310.pyc index a4be5c2319cc8a2c98ee82ada013877f9c7b2acf..0311bf18ec5fec46af2d2861ec4aea3e6ff022d4 100644 GIT binary patch delta 272 zcmXYqF;2uV5JhLk@j8SAL0ZHC(rin!7ube|2Dz~!*}x(vjvS}hZ8!qt7#xI4SfZlh z7C6yP^WXnpGxJTKta4RVK$_p1{b4^>x9>PUC96TBg$^a+!emL(s;tWh5IQdTx!If( zAq9frhTd|`c3{Lx*$y<OEJ= zm4VLFVnUbvO)DhfZ7vzjqp=%PaYCYgkOXGE6Xfr_vQu)R8cu=DozXz4`(DQB#}B^D@nhHUo(s544? delta 217 zcmZ3@{Dz4)pO=@50SNZ}^TuR?0FmfM5zs6mtq|6iXynrkp{Oec}|Q6sBA3WvNBQnfZA|Oh5x~ zv6tp$=jE5@X)?bA*;m8{B3OZhpC;Qa_W1b3oSgXhTO9E)O)MaJMi2qk&h437kY7~d mT2z!@w349+^Pm$sPJzJsQ<-l4*meds zsQL!(3&M#J#?K!VV=rSVQwMUfTBg~!qC7$1_VYnY z#cu$9`(NZ7aQOWbh`hI5WVEJ(mzpY)vEZrEQoIF`>Ya`Vc`YZ4z{(PeO7h*l+Ha$o z2?xKke7{i-0^grc4(#y}II_hW%L+-eH7K4!1|v z07wh>e1CZb?v(I-iB+Ulw;hRNmgf-D)tXApc&*b4n}<^{k_)A4{Wp;;PPaR76(*#N zWXiG#!#=ea^ndvIneMLyxFpRa+XSo6rCk*+ zw`2~Mu6FWPmc~g#@zT322=0K$8BiPnFGz6$e7H>Kgb`){5R+MlMrbg~O;+O;BLLYP zL0cxXnFA1O%vje^;1#|-OHIM^9g(gS&+Xv}B-O9j!+R(K`q$A32AD#f8fYbvJlU#( z6C|ACz1_sih2u<(n=f`_(Uc7h>duzxF+uKueC#*`2Wq!8WhQdYN~0xNOO$5Z>F|^V zmQd#kU#WWW&b=|>D7R9s>Z4KAh?69WR*N6*yb<7yz%HJ8lW|Elvn&ywD$->xgsvKi zQrDEam~%c%C|q|zBn3qg!9&^=y~>tuH1}X}e)ay^^1{Y~7=-=emp7~lS_J+mM#X~v z==KC;d*)X7>bdAfKtdyHcM_iSM2%NB9>8L%_~NTqtw1SKMeTgRt#j`J-4x_rf>iCm z+mN^pQAuHXCl->6ODfc5SBg_g^KPe=HhV5^K*8yCES|n$sj!)d&QeUV`|tC0 ScTB5%A8UUGv|(+?p!#2gsLuod diff --git a/venv/lib/python3.10/site-packages/_pytest/__pycache__/_version.cpython-310.pyc b/venv/lib/python3.10/site-packages/_pytest/__pycache__/_version.cpython-310.pyc index a7c82e32ced3ba6f6bbb1389a767bf855d0030f2..66ab2ba27eea3eb58715ae53342fcdd858e41935 100644 GIT binary patch literal 660 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 delta 10023 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 literal 34759 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 LD-7Adtc?60uh!Y< literal 11498 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 diff --git a/venv/lib/python3.10/site-packages/_pytest/__pycache__/debugging.cpython-310.pyc b/venv/lib/python3.10/site-packages/_pytest/__pycache__/debugging.cpython-310.pyc index 20b1984d3752db32865eb35fdb88ba37e6807d19..32896cef05edebbb9d15b6336584889db885a07d 100644 GIT binary patch delta 5703 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 Q*{8F8cEaw(lh5Y=7ojlz9RL6T delta 5564 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 YA|i1PNk2px0N2W~9t4O99+p4-3$g(!rvLx| literal 4303 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 hakacaK*EXwDG(G_Lve?L=?7rI!OXDV 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 diff --git a/venv/lib/python3.10/site-packages/_pytest/__pycache__/faulthandler.cpython-310.pyc b/venv/lib/python3.10/site-packages/_pytest/__pycache__/faulthandler.cpython-310.pyc index d9a66c5da5e66969eae973cec82ef2dfb1b4f4b7..fa7b79feede289b61f6384e3d1276f4e5d95ff48 100644 GIT binary patch literal 3370 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 diff --git a/venv/lib/python3.10/site-packages/_pytest/__pycache__/fixtures.cpython-310.pyc b/venv/lib/python3.10/site-packages/_pytest/__pycache__/fixtures.cpython-310.pyc index 6eeb36f245ac9ec9d449f6c7002a639bc8a1b0f3..b9f32c026c9ade8ab42c9930317d6d72ff23bf22 100644 GIT binary patch literal 57102 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 J0?`}&`3nq!e@6fS delta 431 zcmYjMyGjE=6rFeWX*5_Ekf>l`w6PDOAcBIXi8j+Ll9|X#+>Nsv(8?xYDYBLL36|;o z4Pjcp!|0uW;u+4lXYL$k?!1MM;X@wB0Rp_wj?)fD=+kZf!-LHMu*g6Zl1O1ihFD^# zbVzQfq}cTscqZB0bMRbp@Io^!nrJ{&O?eY0Yrd}*RG_&Q8ee;ZHPS>X)B=KZ9uz84 z@q|lXksfN-pGUp@ZW~+gq}1lJ%nf*UnieHgevzelW`)t^*kpYe?+*kV1s(#YA>PE; zuXagm!-|p7gY>TN0>5zo7eIHz^p!keT(5Rmi(6Whrs}fJVjWJ*l9Ps$mFk7<;YRhr zwvVuhq16s-ElE_~PZG<^`;jiJfRbic#=T_REQUE*b`pWu;4D|;p+1Lnb<4NbSE0#} WM# diff --git a/venv/lib/python3.10/site-packages/_pytest/__pycache__/helpconfig.cpython-310.pyc b/venv/lib/python3.10/site-packages/_pytest/__pycache__/helpconfig.cpython-310.pyc index 84c9afe8f367c57f9955bd145a77b15cd29dcd9a..dc9731c4dfb5ea288869871f63d97a86af081d32 100644 GIT binary patch literal 8718 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 delta 4106 zcmZ8kTWnlM89sCFd%yVZtnHJF6PDO+)U;L93r*WJEv0ot8lrY-vpLYh@wOX0xS%DU)+SF;0mbUcT7)!OsX_<~~ z6FN>Owv{HjbZXJZSk{r`2uxOU^p;dQOLlhsnV&tX4^E zKGFzb6)64bRuID1KGVY)*8|%4S{F$1ZF2T0eH5Hv2?(Y@92AT}O{4Y=r3wD0##XAP z(-bwSwMA;i4MkUKCg_lk(@(;w$wBUbh!g7JLtTemYcP2dXy*8wGVX-F6I^QboN%3` z9mf&hRL`$m?zh6Q)m?LFpCLo16$Cis3|2U!j?35j9o7v4x#hUFIO|-z*n2r-L0ESw z_2n5CFRsXCuH8G~EH6thBr8rU+4!z@Xb5LItEU^r$W;6wZ9~(b1r_K*Wm9RBh(us~ zq)|1}skV)2r+}&Y>NyVSs#MK5pNOvqAqRDXSf`*+Y&85Xd@RbEZr_7(7u1JERH%AX zjg6q+V?4I&^;WkP*6URx)&u4>c^0Jd9CiilO5&@=rNS7H`=A9y=u9I1XdD!-IXzT@ z!!Qvlks6_{N?YC3h!ScMY3o=%n;I{L#;XuQ&{D$~ z-#+-<(#Q^22e)3_6i zZMdFSuP?$VK$%67*TlRvYiX;eAxT%oo7S-*8p{DGlsE?)JU0j$40r7jH-RiDlV_kS zL6hQaMJM@GIW1@T98AY1XJMar8yKul|5PAS&>n*pybfKY>;SUtk^zLfEza6sIYDQ( z4Wht^&{+zVN#`ikAcczC2DE{Bl^&o6=^-fgMS2){rHhY<5A2^-WZ+~7QL_zod40o7 zQW3)W^VG4-QJ7BrT@wFDEs$Fxo4!>62Y_Oi66zR_>OrG}gG^@9l|#3~KSt3S5Shf(zpP+(Sakm&L>RvEltN zbq-J_v+nzsu5z~rso#LP&;GFJFd_$`1wVoA74YYIWgsK7tK3pMjYhJGtEGla-Al;>U$UDH!o1;D*Mx zO5=}XJp^|{2WBd}3e-2aVn?H3cKdUS!1x`DCibbDz$JB4S2q@~azA9^XN5Tp#EM#R zo~($Mixn~@-Y6b9Bx_VgGS}|{7C?5n4bVQqQ?W*EQWk$H-g^}FS*=dRX+VcMX91#` znbbQ!4r{UQ_gI%d3Z!_XTT+(?pRNERPj=E!nX4`^h@5&?@hN%c%1_&&CF(Rjw7a@ldX1G_h zDH*TJvluIEBQZJ(c}t<%+uEm@Ry9`tju^JFbZ)&Bz+E;G%XmZ&*q4In^|O8YFP^MiLh@2r2kb#w<3QKJU08U&ZA!;+e6-L-awM?s@$+ zKne_;Bb|8n3~QU>3S8xK<}NQ9sc?u??ikME_VPvl?&0@@<|)yAYG5 zXHNn#^6WWuZ8n6vZ4>K_Jew?Nd>#N>mz7{7YcMu(j69>fPp)TckI8s0K;WhEdx~h- zUNgiyJUXhUv9?+$N?}^>Xi6p_d3>gc*Qbt}8@l5x2I4nUkDDAq$u9_dx*@dMAF7V?%j>gca4+D-}A1vXt*Ay@P*fP;i)9v-*>{4sdrr* zsN6Nag8T4^5bNQ~a3$g`F4~m}Vd6&Rz|8HhC%z7=JJ5nqyjMAr!UW9&X63~{E60c_ z7Um|-5Xn?k<9uR#J<&4QsCe+?cl@T4OgK^6|3{ku{*rc015wsCJR<4X(-Y($$~$op+Uhm#P{a+ z5k>rJ{&(ah@yf!)JreumMU}xTj==-H*;-5VNiYvJL%FdD@n&7hpA&yrSa=>S5=`FN2#m1_`2ktWHDtgaXxzYVRqWqi8)lHiCsu6Z`wA`rFS)`H@M5ji z4JCZZ+=z>4w*1MIT-^abi`&s`j;icY{qvWB{b0wZeUE(!kvc$2b$Axo;BWXp2%W7; ZQce`LawUBtP09=Ng>u$>W>TBY{vSAU+Mxgd diff --git a/venv/lib/python3.10/site-packages/_pytest/__pycache__/hookspec.cpython-310.pyc b/venv/lib/python3.10/site-packages/_pytest/__pycache__/hookspec.cpython-310.pyc index ef7a1c330b5e93b3a7f454b664b9f3aec7d2e89e..bc82725b828c272f645336f138235914032dc69b 100644 GIT binary patch literal 44033 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 I*t_@t1LBcY@&Et; delta 10180 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 diff --git a/venv/lib/python3.10/site-packages/_pytest/__pycache__/junitxml.cpython-310.pyc b/venv/lib/python3.10/site-packages/_pytest/__pycache__/junitxml.cpython-310.pyc index 57e518a3f75bef49e5112b48ab2ac1427d29ffc3..5a04d9befe356bee5d5c214b909c1267c3108eb9 100644 GIT binary patch delta 9549 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�-JXv}xh;PUoy&!Ibp^9FT*aiX{3B(EX6Hsy> zhGGzp2_47~ka(S|z+~WJ9d)EUAJHF|Qj@Dga|}#xi)L#4q_zPNZ->fP0Xjfek>5#} zx(_Hb_=L=jZSGb(jHI&#tCHm`J5sf*cKO^`JEGEe$DV0q)^0GNxQ`MTn`TowDtfK* zJ%^q>p=|gNO>sNGIMhj*hg!8dJJqICtcw=6>eRP^Hia##w*fIJ?-<1~ed~_lA$9y- zR|IPJTWItgO*BtnH8VMSxP=vD;_%)M;X{WvWEVt)jxqVs!w1j)6c13Y?ZioC4&*Sd zL^D)q!qv4?6%nu(wS&XdtB=4%0(AhDV$^P0)M{mvCyunRi*oYFLDQ`w+3F=vQA_HJ zIV>2EW;p(%c@z@zwi9;Uo~kN{zovOsO3JA#M=lEyiQZ#T`JqV4^2aiEv_DE|0EG_P zqV<40c64XyQI)FU5;m`?>^#VQD8FjcL5?zPlZyowF_dW)E!3lfv+7Q{tB zn`^|vd_^DEkUh>8y>m@PA5Do-&8Q*=QAH-2f39U-Bl?9)c4V%KM{$h^6a#auyqd@6 zaSH?9RP?HmST+{CrWnB6m(AYC)Me_;sNEU(Uh}wBye*pbp^IQg-}~@WmAzW|r^nXE z9wxajMT}?UKONh(DGws<^`s^!-cq^DqPlV>i#)|rp#>!#cpXa=koi@LPEI-dJB!yOT)=oR2ltq>4D^wx=-krNk$~7o2e}M*gEYo%Z zb^&1mx9xb)bhihcy$zIg`IzrlfiNm{_)`88k^g;sXGuiSQ>AkYRW7@MgDP?RjW27gjTiy_ z)u3qXyjRo~*&N>ObGo1EPijx=q%w5o>fDWSH!pZM23@Hqe~$bDwQLmKJMsGPc5`nh zUT0My!%aSDigN`kV_$ZHO8Q9)#alYHiZi&6Wbg%A?=nHiKR?k{{Q>fDJRpN7Hm3f=^8_=*Qibr4)BMQ7!Y zNzhAKq4*39@CkJ~i`pa-i}MgeRTmMTrk*w8IU;GNpIYJD)E%}`8-jnFehFh;7X3Lvf!R4T(v7He;JRE&dWqq zeZRekEhg5O@cTq9(am*9vLGvM53Hubxh|g=_NK+RI(`(73u)AjLrIiN=#^12DWF?O z?#nMDD69>n&ZUKiIQ|y_R$VVT60iV-M#hzZOVyN+Hq~ArCCZuPIZOsCV-YIFeB3}yCMW(Yf4nf{N0Tu zrCQ23UZjPSwTi6-CW*h1F3enZ^Xr%B*FDSP*!rkwQ!|F_cUyUYo*%mnot-o2wsbMLH9&GS}8PS YR7aT6h!+TLp&(#yXfRY0^4Es{4-auU^Z)<= delta 9299 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 diff --git a/venv/lib/python3.10/site-packages/_pytest/__pycache__/legacypath.cpython-310.pyc b/venv/lib/python3.10/site-packages/_pytest/__pycache__/legacypath.cpython-310.pyc index 0933de2761978f9a0ab2b1082ae032b725a1c001..8f99dfa606dd8069e37467e3f4bdcbef7f1f1d7a 100644 GIT binary patch delta 6844 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 L+qB@V^|Aj2cU(aq delta 6662 zcma)B4RBo5b>92-?cdwgYPFJ9vVNZ=%hFmuwk*r~wJlk;jDN5J+Ymo6(rWLMwf6pq zyDMzDZa0J7&;kimwkbGf+7a&gABG_#m`YnG@P5m$crW;X6;Qk{ zUG!!Q%TR(p9n6HRkm3XBicHuFE8a*~W+GNZ@j>uaR+ZvI;Ga`F|eZnNh^HkHdl7d~buq0HVU5=%?AfW|YONG{sMETpoD^fT{k zjBO-whQ#Ex2a5Q$L-np~&_{|s+r#~KfCspNNd|f7vez=~AP?A~1&vqmu$n~0nx>LR zlqL*K6^~waVd9nGt9gy$BX;CW6|Y^B$Ws)aV!TfEtA<_yS<0(6@sRW5~^XgpXc! zSv`Er?!DFmJ$mhKW$6H{k04cqX+I$f?=5_a@44kE*#Ruo4BwAU8GwUr zkZ#}yFw9_CI>-+}+777`ZHM`dkcP_A5q=cX4t_Vkncs4m;Yc0h$Kij3pW`R^tpmzq)JIm8p+-Wpqs9{Z?%b(NmhS}y!4DXNY`?UP0q~zP#=VEA*EachKQg`ixiQI(%CTUxe4~{H zSq|RAj>&g}6X1tJC)jQB{?LHiC3eZ#%EsWVI@GBP_FTXGW@u~Rc5*cdBDIRm=+Re^ zL5No>CfGjt&!8?@8{P%Y@$e8kDDMkTz|#TQ7Hc#!iL-Vpo0{vV(8)^|NXb`Li$ij% zr`k9IU5*-Wlx>x@?691wq?k`tjx%L|$iGxMY``as;_O0~e6^uQYLOU*+z@GUjEtMm zc+T;1EV2Xr?~Ck#!FOP=U%pUR&u)?Ksxd@%vz)Bzsb7yKNYNC|7Zy@!TO3mnoq(Sr z`8qB?Q&mrEhSA5Nz~~)n^tY;}*sXFPIqPN#bo2T;oLySs zDe<9oaMa?Y8kLs$ey8tE)xD~3vATD|dZeLbZfRxKKA*@eAylW+9q90&)9Gq;o4mP^ zws*AVaKkAmOYVd?E#lOy$Y$bZ<>NKO@IH=pXq4~Nv}5bkj2yhz*eai{ zZDUE*!sVN_w4$xC;qs2y@@#Afn$N~K_RCvmvIW^yHwn#RorU!#e`nUNucM=}uy^U>%_w$X-20`UTa+Z+) z*w~Bl-)kH#Pi{pXYNF}h(ai&-%vwbl-11xx&;L-HBfOUSfHX)q2f~WfcH1a z*dpV~-vavzo;;oYur5o@bT~sT_j(9iugQm7I@u*!AHmZysTS_gB=D6oRw7e;AH%<02 zdC{azc-Wi)|AyJ^RYRyvFdl%}kIB1`o7?uV3$oCLLQY<3+r=J~r8d+132J;$&FBxn zzKuuCD56WVeGqH#d(gzS3yrh}-T9@&x$IoO_|e{j+n3H&KHE(B3~S>Kj534g)lq&9y+K140fUNxScQHWc8QZ z;+oiq<|9tNWa4fJ&=LA);!J#;#QpMT8%IVrfEBGErOMgaL^hi%B&f#8&&~>ywEBE5 zmli$5`ju34_6ZE&5-O1qz63!@5KvI?rE`fwOb`+TZJ+o< zB7a0AP8cb+fTexT6D$muuIaw87G_PFrbn21+y>L|=;|F|VYi_hdRPyK!g`e6_!m+% zqPOUNy&Vtzi|Jvn(`rC6bj_AR%imk7K5U;(Bv;B1 zyeYkWI+Yd9y}nT%>B5P7=1f-=t89Z)fR9GTt3zH5)X2xWeVWGP6W!h0{o)E*#G^z$ z4N?l_35&^$U0BRNJ;i3tmX!2mvUX)rs?ni}$5*@{pk4JgP_VWtTqdxTVFt5BE_gID!H6Gzr zD2jsIk9Daog^$~rB^=6`M6!?*D~Xu=a(r9uSTdc+=f~!jR{EFl&C#FEB@=1+cKo}= zQe?J}St8}^g41P2(3+JhuTzV5Xq^N8Ybz|#Nj9{PIQJ3azes#J_xw32l$Z5eQ6B9H zpolx!v(+g1v&)%zTi9HFx`%G1=XzS?S9?wQPETFeubjY~nVUa@&!hgh?o>EmlzqKT z-Y?NOUsl=sFTmo9nTm;Y?>6{+ytjd!l+X8$kN!VB!a*r`+Aho|@^%4BE1pC?tP6^# z(17o3%G`(gTBqaecsz6+mc(NeNE4CI5cw<-N@`A0qI@POK;^3pt1m#tZxOCMUmP_J^)MyxTRYznpL#A@b|!6~jakBKZdb7`*pAVOiA!p`LGwz-*DDA>XrTHEaEoeGp0uVO&C83%KzE+P%uPFbHKPkdY|~l+H?j+A7o#nne44R=D-tWTtWc<_rPAf0MFv@ zK}vdIWyvmi?oOna?IaaHtA8Fa?(!qIjG>!+dMv&g#MQBG_DN}s-_h_AY)alGATV1f z8L50Ki-EF9TRb8^I^G#n+wx7=h;PX!$LrWX$Zw5z);&N$({lUWVa?C#S(HUI@!DGW zT@w@Rt7;=IPK1JwP{@J{()SgA|bZV1xUJ>LQGO zb)qBq7$*;@Go%r5(W@hJojMd0jcXb~73h5OfKS$j2L>h_Eo;uaxi^S2kh;I<7 zCPK?7UL#UR+s?T_TElU*a~u>2e3l3&=T@%y1I1f2q0h2&lQJ2gdchpVHydbf*b z75=BEs@UJk6H{$<|4nAzB~?DrZv)xHX}ygKP6 zFTjU{r>biCi|Ly?4KgHLl)|%1D|8d|Cv(|_)M;n=DFKK|nciDpq#^?)Oxb85bsj$f zVI)>>=+{^Uob^dyJ8<+=Nl>bCCXJsI3U-z*j=X3lAHF5~{1Wa-RkEsr zvJ_ZbL?=h8Y^OeK4l1xJh$?hm2h4R$uE9|#06N?5BzCo5$gz|dpWhc&U(UMAkM!zi zA*8m~_ifo5V!9S$eob@ZKSzq;lUjw2vl?!SnNfe#6E&j2m=QHJW9_4W86QL|Vjg@8 LFs&6&tcm>}QjA_9 diff --git a/venv/lib/python3.10/site-packages/_pytest/__pycache__/logging.cpython-310.pyc b/venv/lib/python3.10/site-packages/_pytest/__pycache__/logging.cpython-310.pyc index 62fdc0742b35e531e64353e3278d391cfccacd43..2f674cf1bcdf7c2079db8d9563463f1330a8ee7a 100644 GIT binary patch delta 13148 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 bQX6sGlTnA@odn#~ex+EmWr955uH5n8nn$R1 delta 11394 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 Id8*d@ANTszPyhe` diff --git a/venv/lib/python3.10/site-packages/_pytest/__pycache__/main.cpython-310.pyc b/venv/lib/python3.10/site-packages/_pytest/__pycache__/main.cpython-310.pyc index f7cbdf37303ea229ecd6e8995d4ec516e397c3be..4449b2a5750bd876b0258e85c5976e6143930ce6 100644 GIT binary patch literal 30014 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 VW~4R2V*RncSS%ScqCC>U{s;Lu%pm{( delta 4044 zcma(UOK==Vb-HJEc6RnJtv)NQfCG1C=rdrr?r8>?(@jD#*nbs;GiHCk~vZ-~y*ysG@QKJ2<@8tCeIqR57c0 z^ZNCBulv1zpVp6--e2r^>9nc9^ZRG7*ApicRQc5$4Zf-yYv_(nkXLi#4Z|@c9s@k#BqXi_ZaN)P4&pFKI!TEefTx_4#1nw0 zowUSFz%x!p;z_`>PFCV6z;jMc;%UJ1PF~^}x3AIf^h-SJ78(Q2fW&j|U}MM`A_}Ju zhC3|(kT^x2V||;%8G&yo4YHwo8c<7Yn2!N{d`V+PHgZpM_OVeu zE~%xh8H0_nahS2#KA>2<4E$L>1~L?M>8#8qV0Pky!Y0|&J%Cf2)S2Xyn~F2V`+>Zl zO#^v)lfr`g8NI65GvN=&k(cIe5@l*$GvMzAjcT)clMA4zuQu1=leoZ}TvP)OY1*Z_ zAB4Xq)=A@XE2w+TstZ)ZTjRp@stoYh!fMOqfW#NpTl`uTrDAV2ffbRZI!J_Hp=a}t z)F}Ce*Sy8ouT+ED^89@G6FN+k#~;(~BVZ#6v_1fssU8gL6)?3l1{17%!WU-Cqw z8bn$EUW<%M1&ygxZ0$8MhfE^~4k0Ju-7RnLTTB7@yYTwRM8O|gJoAJS4KVrz{51M` zcq6&5SfeoCdt!j#dk)_4H_01hG)$zvMJB_msdLF)jtL*828%Ud@-qPXNQUHy0lG5) z#mVqbsnJe%VXi>hDwwidg}|mvg<#Z}22mD^^m#Avc_8{gM-6g)`#~IE+=j0KU<3rf zi^eH9H4r&1t$hTc=W};ykLlp*1|lbw+ud=`wla%)sP@Y%+KPA9jG5++<0f zWGS9vX`V(uESW6BviD-Zmq9*e16LBbOyElPxH`a=+h)tMJUG$hIS#Ct-@u;ZAR_Ty zVj$IrQlNTArpJdeCQnJ3-ewHe&kDewX9K*C_w&M*_D(hJA# z$za2*2x&imXlZYGT^6yJQv|Q>V-|RIgq3ld(`-WS z1cfzO!8B(Sn6PMRP#;x@i#btv{Kw2A62mx}4>xj<>*3FHm&d;W<6Va>(-~r;-mC|e z%4VqMXGniIn;(VJa3TLQIx_(LHjUJp>qn%qS2Dg;3x6D{1-k={tDApOY_009a&a68 zHO!sfgLp59Ka)YMTvfzbpi0e;V&XL7+ls_R9HN>RR)&bjD)9@L`wY`nGr~U<4qZ~j zCFI^#Dc*QdSj+?M6Qo}Tu<+atv0C=B{qe%UNBV_3eJ@reI~&F8O}_<&OMDd-VX6q3 zOj7^}hySo%24x4zfByfJ$*t~H28N-sx$yLa-HGBY!SR5e^DcTCJ&f+~ zR{#V`n+9Z)`lA6|Q3JSet;E^{XnGrN4J&a*??cmLD%0*$q69FrNj7L(6|-#;7%Pc2 z1uz&7DL87AqB5kwyi#~zb_s3z5V|RgK{o4QcJgo6*REkwS>BSxn`?F9H5?zdKb!B@Dsa0G?Y! zo~@kPy9gXGEOI5Bm_C%jlkDY*&%(E-zkYNE3bE$ujrax}6&#_~hE}yn!26WNT%(b| zdre#2gxV#$t{%?KG&^+G6HQn~)cUmfsAdvdn=W7J<=9E&y{7jYcSq_(BO_DbVSkXB98#$ zUdE-sJ|Z}RB?KM<=|i+)mu1q;vOwZZeidH-JphKHQ8Pt#N(Kon(imJ)pHfPnQI-A& zuc<(JN;`V$2~AM_OQvupjY1{}nY)E_^9j8OGoL>{V?Ldjp3<$%@SvF(DP~OhkA*!g za7W!(p@Mf$vEf;O5>@FsmE~i EUp&oF?f?J) diff --git a/venv/lib/python3.10/site-packages/_pytest/__pycache__/nodes.cpython-310.pyc b/venv/lib/python3.10/site-packages/_pytest/__pycache__/nodes.cpython-310.pyc index 0f241f7cccea0620f2a7cffb6532417f764ce616..200257b84fb042938077a0fcea37f74488d6398d 100644 GIT binary patch literal 23500 zcmd6PdvILWdEef>15iOK62a6ByH+OZKl&! zlW7b;)Zgzr_ukzFAlc4e0R{)>p7%Z9cfR+z-O0ZjDq%WZY7vg#U$BsXba5mAYbUtUX>Cm;4Cw6O{?cmykbDIUxB_eW zk{?6wsB4!4h1j!K&Yt=rnSS8kX5B=UDu z?vVUJxOKdJPvsuTA3^@!%Ds|5+Pbg( zfyxIYe_QK=?Gu#~lE1w*)4soQzvS;g{zH`yN&Xn}Co3lmDTJUPUQ)doK_F02j8?SAAKsN9#Rj#kx~zP_S)GS>B>2j zd{{kzk_S-oq<2m|@}^UnSI?^-QFG{h9`%o_GpIk~&9lV~r*gq7s*k8A-n7($dQ#25 z;b6XtXm?&+K)Vaxg{a+AD7&bhLfKQ^Q&HK|D0^BxgR*CmvS(2CG4(9Uo=xWXn6fXY zW?p!kI6Py8BlS+F8`Ohlx8pD1IX_=-wdzYPkNIZ;PY>mTu15LT^BVz+pRcd4H#;jR z8E@1ZtDdT^>+ZUzgH03_T6&`@#dvTo)q_?1PoL|s&S}*B0RQs~-50%JLwA_(Uh{OT zTUW@BF1~!}LUsP>3-ixDv+xuOa*Lbm-b-~(KCtLtVfOAB4)J?m{=?`q|<;WCEC|5AO)@3uAqFB&*=IjH;G!O=^b zT*oKsx?{H9x$L!;QIWgs`98K`$C?f8>S9>vVB~6mecLHAeT6e!2^|i&GeUT{Tkb-) zS)j1wyxB>4_kZ2%@SD8uc4={g%$^A&)O6iC3 zZ|CE)tKGIYd$sPjPi^>~o&{j8VtX3i?8S2z&dpx)I@e}f&86A(&0w|Ld8qur=~=%S zc&E_hYJJ7?XRD|IfM&UDzr4N~j#QiN_131K$QjJxb8-2MW6AHkb~^i=d^T?t@m#X= zmVN})VWCQJ5$i*TIlOUZsk_lp4b%?(5D56O{F6w%R0Z1%o?}(gYD76I14`ql(ic+8 zmMW;?8>LF-3xHcCi|rld`BUEWUQn<9LohF98m+qTyLfZuE^WGV@;Fyxvzi-pT@PnW zH#|33tp~1ZYOfKv4N#6iZ!~xYU0rYbo?mqF6L;oe@l>r`l+wl3rtdC!tMzNm?uK^# zjddOgaV7& zx!@(wyi5~o^y+GE zUpEW$NHFSp)2%m^i_LSjw_I=F@ZNH#@rmSZyqoF#t~p&cP|= zU5m1_;106b*@t8&Fcz0}t?6ASTmVBE4y{%+Xqk)cSC|>=jrS@>Df8;G{WxRNOB4Nd z2}#33694dY@b3kl5FT`8t))Pktgk>i!+-lLsjsC$psek5klsr3#E@926wXI>q4zJ& z?}Ou5>!*oq+wLmhyySUY=Q_?pJ@C{?7dux6C~$_NW`p=zE)MRN>NZFsxKHJ-UYYee zvmk5Wth4ceKnuUT8nj#YbZ`bw4W6}AjaBT8r%nakQ&P!e0XoEEA^Y3z0`T0yX3H;! zR{1Tk7q0svh)E8!k}u9U`a$eUSgcmtIBqQuRPrGd=sS_jIAOZc@ph=vF%AG`|XKr?*O61b5V}l=(DKb$qP5CE?&|1;c*CB85B_3 z9w)TEh)k~-q;3FOTUHY+;uox+us#9EIH|zioVQZ^4bp+L1*qNt%lJ}C=o}c%8~H8! zX69DveCh@uOQ^Q8?c6pb@F0J)u!SCge>Ny?rEd@ua$kd_bqg~4z-(#GmQB5k`L3sC z@(X;`W~Uk8Wd;!}=7R^hWEV@uLQ_{tpR%{DZF|f5VrngQEv1p$vVYos6{44H9vK)Q z1z_Ne`ZMX!E}z!t&^xrQ0ZeHSqJd2=o>9$4z(e`Ot7zkcR;S!l@813f%enW;XIfoA z!+)aO@9;Jki|LMB5;Qv>Au8}H5+Qnzx;WyH0fDhRXLX!@3UQz#AT|lYfpXP5pr4)A zrW@3+dcNEHGk<(!9vId?)m=W-*zf})uOS(N&HlzxbihyftKE$j@Ep^gx(fcj;_t=p znUOF9X5wjmp4(U;>%5?KSL>)EedR>tss

j!{gD6`&?DI(Kq-0eAtWy(Qz3GxbBI;6 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�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 DrlZQ4 diff --git a/venv/lib/python3.10/site-packages/_pytest/__pycache__/python_api.cpython-310.pyc b/venv/lib/python3.10/site-packages/_pytest/__pycache__/python_api.cpython-310.pyc index a4b58500773b0441445e6273bee3b96e340d9ded..6dd82392799ee1dfd4f2d8be346c8d76a7c8d53d 100644 GIT binary patch delta 10036 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 literal 32600 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.10/site-packages/_pytest/__pycache__/recwarn.cpython-310.pyc b/venv/lib/python3.10/site-packages/_pytest/__pycache__/recwarn.cpython-310.pyc index 3f0f8d17ccdb76dcc454cfe33efaf07d481a4645..a987efbca425740724c003711960932ebaee3dc5 100644 GIT binary patch literal 11901 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 brVImL`Itjr8ELfiP<`O5k++Qx#`OOKEGq~d literal 10629 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 z z*Ej%UH~>iTY-d~P6 |L%l;HB-w=cABQ~(`@Ws4J2+}7#HxppIe>#A&n!$*e$vUE zgO=jH%XjO}x1@E(c#G_Rtoe5njbs1*IMoW7XGqOpfDF-OiL^2G8_8v%UPj8`fD^c( zCCoOc(sq(E6OYP|R@E5TE}H243itUH0I`Zo1&{nf5&@_RKaGHU3SR{G5k){A50Jep z#FoHcELY6g!`8_A^ZEeN1>zRGO)o7rTJTQ2ei$egh;P;d>B!T?HDDk<4G+4>@V?5u zlWsdL{MiO@Bo|LV1NB6D;SQ;%f*3k)AvFN+tn$x0NJGcer+5}Gw{;-Fk@mD+yJLP)2ViyotpY7JORV z{tZIZ>8CRKBf=k|=x95lLCMfED#wo{ov1hvLqEeict*8IpiSU{{VAY{F0{x~dLMel zSzz?A(l0xnd`FQhD+~@y4(k7(+C$mz!iq}e$?6{Nu)4MRf<66U{f7>a3??r6rjX1& zT=XkP3R=6UNAT^;K!H*=0}4q(XZrSvlIE+@@#+NyzlU+56yfir^x;Iqq&MCM0yMVW zXukMEET=1RdQ0x9M56h`lbFQw;WgMWD5m7ian0n5zKz;?3(mR^d5fEs#Za`yT(HsR z%%5e>(TGe(q+~pmb=4p$pJ&K^DRvSyXb4+j_O>Fi>f=>xl(lO7jhX%(xD64?!{6n) z$mO#P3s^rCVaY(TD$!FQs4;X@PpPHifn@u5a*3GyChD{(7!!aZ7cc83FDOq9#is~= zItT6|79xISBo2M+e!`L~&Y!|Yn<34O_C$t2GZoMA>nnsR1|DgCo3=7^S)0P>mq zHbkd0$Ou6!#KN25_RUck=1DZ}r-EP_R>^t8ZMJ6ZSD7R!xs7^n} z@kQYOJfRg9qBaZzx=mE#7uf0jwe3>-_qzB0eYV@;s?mSgtxd4}AuDKX#?Sh8U*9z< zN|JEf>q}_rDAR1`midA)e5}BX5sg&h2qg5nV7h$1qM;f zQoD(h8&mkBDUky%iX5ys30X8y>Ki)Gg4mE$U1V3q-jeAOU;jrUC|=hRdKqPxF%~_h zO&W^1x$t#i>7uV~Lqjyw9SSp|12QUfYWiPv8SYf_b-O_CP zd)c8!W|FQYvBhXCUS$>D*YWocG_|$(uOi5ni2V6Q)_scwB~k2H z`U66L%;GyNL~|p-#^+xct`wx?C_57;M>Fdp4h$l_N&c(>$+Gul(o^y;Y3*Kj>bt)# miJxgwRyI}08*jLexCM83bhq(_wPoBKz2D8cLvEpP>i+>Yr4uOt literal 17537 zcmdUWeUKc-b>DpL?C#v&-rffe-@uaiJP^182$7w;(QdaCgRJN<^vdW2_xRTgTCW@<)lpU+` zaD`5QP8O0vyP#8r zl+a1g=|WoQ6lkyD37rN#QWz211Dz>kgdPDsS{M~N13FvC3Ox#XtS}~Y7IdzV6M78v zcwt=V9O#L{gwW%lCkvB8PgJK?w-mMrJqdccFfH^H=&gmVLT>@Rt*}k#Y0%pX+lAf= zdPiZ0(Az-oEbJ6|JLp}7T|(~wy}Phm=$)YV6!r+c3-sQ?UZHna^Q(^)9us;`_3_nx zg?&Qrt?pkvP&goTzIt%=P~ni!kAXg1I4tzz)tS{Jg(IfnkL#~u4)#@_SUp-eDrNht z$5v+xv!D;CgLjO=oIjxssl#gKu2nexf}xJ6!*>jI__kFzp`P%cR7d@se`33#j`^dD zu9{VIcbvjWe;;ZdS0_;Ogn#0;ecLR&NAjLjCy{rullK&Ay+=KToTvP~x9!5y>S=${ zf69N)q9rY6{gnTt@A7WDvFNDxs%Oypd)52;`FL-(|BTc+rQVNP&!DXnDDAa?=jYY4 zD0!bc-Ot@nRBmL1vSgF@aRnUpc&4xVh zT)x#{JENCB`O^8~xeuN{_u&f*FCZhyf{&NjZsKD#JO^Up{LQj2jhtI9RcaUg28%~~ zxfg1S^`VSTjWmDH*Fw$FjP-67N@3+XUp_8b-L~1vTV|AF=JS=R|6;w2iqe6MD;O~gaG&koe?nKlLgXYCSq#EGSFbHr z$g=dgdbR48J1hmOSCQ!i*D4MCCST-(@B^XK4~s_PuC;L}K2PTN>9 zK4Dxk93wPu9WxF7L!Px}Dzj}W3L=!$kKlLOShJL~ZX-9b-9YLhweXwVZrpPmJXJK- z?6&P1pG12%jG5FzD|H~4mH$!Zx~~K9saQd$zGm~?TR-)gwJZ70tmTg#%fEChT&~x6 z^O?2Cs;ForxD_<2rEsyXS0m5A>6e?KNR<=iiZ|-|nh!C=@Ji)nKgymBA)!~Bp?_ZM zx{kblg~(+KrEtcHoLXJ^k*OjZeTmHAEknNt{dnin+;V-@pSxBHR*y9UU(fOBpn;j1 zd;aYCvvb$|+V#0=DLzvRU)q1n0$|xNA z>+LYVhw1eV?kWZAV}@`0&UV93D07~)>pMs>J-)MOQ-`3$LP<*5e%kkN&l<_N6x7Hf zCKh!Z)X7Mh6YC!2C6MRf*PS<162B?@ru|XnDI1z)L}j2ivi_KUNsaoB@}LbJl~rTN z&8eI}-l;dPCXh3sCZ*2g;5$%%N^L>ul)nYf{Ak|VrE z>)0D4H?NNPkKz6abrhp`9AnrAj*XIo$UUZJk-OhNpyvF8{vj-@<5GT@<*0iIyT=Lj zq_i}nZ0O&U`Wei5

  • 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 diff --git a/venv/lib/python3.10/site-packages/_pytest/__pycache__/runner.cpython-310.pyc b/venv/lib/python3.10/site-packages/_pytest/__pycache__/runner.cpython-310.pyc index f5066d716e9af56efad569bbb6eb223ec18fd1a7..4ac42b0dbce494b9f6c0c7e80721cb07efc51744 100644 GIT binary patch literal 16226 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 delta 1210 zcmY*YOK%)S5bmDW&SPg^juY=A!cmeXn+W0qAQ3DSu~}>@!nU+<$XrIlRBy7%?2Oej z7DhSvU~@o%Lo_!e?jUghaRhPaFEsoDP8@RL5U84!P28*b`m5^d>Z-4Mzg+u!t&#b@ z%W(Yma(n)P_q@^5S^m}YI~$R?s0B67L`@EXgO%6|Cvebqpw?x9D~v4$HaMpY9HE`) zCp_>(U3fVA;DUF`L__!|`~gEx6YAij$E&OsG^T9S{OO0gy!?yzdiLHyG0)N{8FA%* z{Ahm?j<+Y{hdaAlCD(S2sl5C6<=-F(7I0vJ_6Y+X=%57#=)weJlYtpn6}OOM=@qw; zJAqws7r7TW6|W=jLj#(pM&KfAK^vI|9bn*#`pke8xP%^GG=|P94B#^8a78p>O|(R_ zi$TC(PJTNl{FQ4j+@F~^mWe4KlLAZzx>+x7Wp0X5x-P^VXog;rAR#ysl1F# zULjZ`=%2%&{M~%78*At|8wL`x$M}`kt?O@7nvo&9ILUPc<>n%ph4U1|k@B+QiI7X< zLf&WAJ3|vh9!q6@h9M%R#Am}oLjBSqKfkd4;16yglXU_rR#~%5E}~*&DLqbdd6O1~ z1g{fNurhNJ@xq}i&ZM2>jX8fyht@ZZl|z|@j0z#Ehw5$bY;A|r r@nmm*GJU0}b5WJOxv2gS?^CBasbR3XvlJBt=C-Ek9d6(6@Q(2x#d!TJ diff --git a/venv/lib/python3.10/site-packages/_pytest/__pycache__/setuponly.cpython-310.pyc b/venv/lib/python3.10/site-packages/_pytest/__pycache__/setuponly.cpython-310.pyc index b2f94af9c25c1d4fc80ef5e57e480de26494903b..2372c9409704ea1fa1c69d1090b6efe639a4cd38 100644 GIT binary patch literal 3165 zcmbVOPj4H?72la%E|)7(qGd|197ov%O>8ETm?kJt6owJQc8VUVxR&AoG~DfqGbESV zf0Ob8l2+v)hc3a{H}huR&b;?~zc*>4 z;TrJ#>&hQPzYcHgTzo9(d}bI#{sRPNFcKLt=@No6jc9CkO)Z;|6<4|yEnATtSG!d$ zS3q{Uj+X7H7Q0^9j4Y=t#nSluhUIa*~` z*y>BdZn9sojThD_>0V{G*md?6=v%DKJoYX~Yvs)MVCIMH*X(^gbDg<+M*E}h@pNsX zmVzWn^B@n?L_)Xr2cB>dIiJ@$^iuUmQqoc`Wt7M;<@sy>ZUH98M+MRM|lf-KMM| zkFv!VO#7nbbGFTXP20R~#beySLz*w5$SK3r8fh$x+ zEBjK;9EX#Fke~o4=p-5Ek>t1OXGa zhYwLL~pA-(tDx3GS&u-?_y_ZQax^#55uGLDS4xvMIWBtZ;0 z^I-qWhizBc1%j1b;6T-Mq!3rCIt*lx=R%nwlgjA@Q4}0RT)YhqQA^s6A0~_+t9mc! z4LS1*+btiL`(=3i0>*7qY`{A47B*->BdloF(O|0t#eA9G$2*{rThP#NsAUtEy2LhJ zvP_n#O>NR7^&c*e&>zGtSnx(Xkz#eq-vynV?fSt@gIYZ64^gp1$8WVs5BYudyCpSaLkKU&lRNTMNavI3GaV>wW! z%ta58J%1b!kSn!U{tW`6Zw6-v+?rEU)Tic*o*77jGxN+iv8L9{!hT-K?Yw$QPRYyx zPu5_51#~W>(@M)YgR%vqOU&FbW_4I+qi#x>^$KWpYQHj|HAanB#&;Dxmood1ipTjf z1EPI_Ygg_YX#J!*t%}Fma&6XR?(E96dIr10RrxB!W(i^wYJEMw3hP>|!IocA_`=F} zrnYU(*7ONXF^1EuqdbpcySb~DdqIX}B@PmxD>0#-C$Lsb=FQ_v}S4Bz;gG6H3SYPj#}kOHIAosX3nh{0v(*KQA>EFNd#&Y#gV zB9(q~-I(0`xkTEz9i@0rd4rz$MiX4IH%#M`Cp@$Q#RPp-24x zl3uSb1EJS17)fAA#P-6Ho@u>itit|7w0}*xzTY2XmioRx|EQH-8bwg!@lv@H9P|Wc zq-vFSjU7Kscji}1fp}5Nb5&971d^h_TzrQ#adoa^WLC6G1sl|nV< zI*p$K@OI&BeOpn^Fij7`IE!>eDo9zE0Ce{Sl<9!!BIw+XQ#OwH9mImfN>MlJ6n^CO S&mpeqHtWP)bC=xBCiyQKvn721 literal 2994 zcmZuzUvC@75x?C#9*@UAq9sZeWh-U@w+T~AbegxqFzVV)`$W5u-3BZS=EPl+N8P{d z9xaKcfCTcZKS5D|dhCa2KghoJ$*)D9+$8SI-jS8n@Nmo7nc3OhncvJ@T&=nW{QmLd z@BP154C9|Tx%_ir@*8;N4lxX7FcKIc=@Ei71)BD#rcI#Do~daouzD8A3qc{Yd$#7; zK{0fCj;4!1mwF{lJAoUPdu2_Rf=XEJRW**2`-TydLxOIx$&BdV0o82V3Bf@Z}ZZSHVv?Bv5(j-ZMn&;1Ebw~i(YFJRr(!|xbV_gz{Giy zq<$QE0nml#5s+QvN^i&oPf){t7)L{Ygxtp`e)=$G9Jx}Y3vx#0WI`BaCbL+9 z*>5ajXikwiz$s-MX28|lt|~KrI2{f2QD^17JqbG{aTJ`+D*0p&XUdguZ{APEl+W5v zfYZ{NtzmWvd8ftVRurc#e!>USl($m2iELA4djoWovc^0}Ry)XXeEh#1^z6<7@N%p1 zaAml4JoW>goAsm7%CIxy=}5%WM3p?oVjYe)6##WZ6|~@QhS-GJyXT#89P-YQC&Rrd zIKOioizAtM1K#=K!Q%&=mpppe3H-xOa+;3g==1&0?slY~@;y*F@C`XGV(bXMNk zPfk@mWA<0OuVE;5U~q{=X#UvWTlSK6zZcg*{0t&o9>?*~vG9_F3+3isWPGTKB1eJQ z?REzk<>k)=0^b3=atOnBkg9()baaggnNaarYR(BG=Tv+;Hzt<2ofh!_oQPdOhXp9G z0UtDSkqfh8(|{SQnb>D^PUlA7NlSA`|8t%C2VfsiVs=#lfarw)%g4{2KW)3p&H$tA z%3c4c|Z3= zKSNF16irws(5c#j4rn%Pp}|HTgXJ=Pj$0s++c3~?ZObJtu}zoMNR8UmhJWkd;uj$N zzidL^KE$QJg#kPSx1SPm11RvV&MFgpx{E$V9}^?BGhc~YbK~nG`s(tFesi8Ze(>d^ zCx7aS+hDWpsA_&^!R0ha)!OpDp%($rgxCc&@evMq3Gs6rS~%$SH1md5LS$cZ#O@VG zB(Y4vI20&<#>MBLEZ1Rhi31P%#39yyENTH%fUV3>jy?ce5xDqjhx`i&bjSq2Gp7@} zFm<6Z=ccI7&4qP=Af$`Jg>h!ht%Z&Aw3q^z&dE7hAl%9TyaLE7jLr)U;{y7Oo|%*C zoGfb0x*(vn#tKbiQNO_5u0f$PwH{%`BPza1*P(!wUZZT|k%5-a?71zz(stZMm6aEb zIRG3V*nbm(Q-M@C((-D$1?#S3cX>nqVzByOGuzf;Tc6$($8eJ-(#s&WuXR;z;3Zh3 z^Z3kYt2NtxB~zZ9wO&aq((s>y@9ZtMRDnghZ7K_uR6*;gVy+@?g77DSCqW=l`W@Ww zMG4`?j3ZOfIl&bFhB5-FNOdN3b=;x0(*3*2^u*|DR+R3IpjDkD;{H=KYr#Oj-7#h# z|5T3c-wR^MQh9%WwQAOAeUhJ8wxD9NU$YD-RK!Xwrn1@WO5^m7NR4N&Q)T!e_({;`1iE0h)Z z5yw%c$`Sk}hu+CV8MaY{tcckC%)ksK^VUtIAlU zoGB+~NM%i8Kg#l~fLFj|Q-xtLm1DJusT%St$88t0RQx5XV?IJj^b)wPqUo`!=ridu z7p2N#JdsyIq%->-tkbRAAr`8fPLlQ zY9lW+gOFix`l07XFF;S?#|U1)BH|A~ws(}Bo+i+-1)f4+brpz2_^Q#(R5kLhy5INX z{pE*~zyPS)Qk3->fwl#fBh~y+vJWxDk0jg%vrpBR@+E#n3D@GMu&OR)Iy@Rix*sbC zo3S4z0W`G32_K}oLFAPYe5<_;4J}Ib)h@Y2S7F_WoO@x+rUAc?ewTM)s2CN9s{`@6 d{H&X9of4bS_wZHl-lmlww>EZ4?$2(M{{lv93N-)# diff --git a/venv/lib/python3.10/site-packages/_pytest/__pycache__/setupplan.cpython-310.pyc b/venv/lib/python3.10/site-packages/_pytest/__pycache__/setupplan.cpython-310.pyc index a266cef22cc723c71095dda8d98b950caf2c8ed6..6b45e1c5bffdecd415242e241da167cf0212f01d 100644 GIT binary patch delta 729 zcmY*XO>fgc5cT*wYbR|AElC<^fJ()eN=OI^i38$Ga483FMpYELPU>QN)3pOFLdt=j zKwR)2fZxI|;OYYxxbO>*a$;uPqOz8r-@ciBo|*M8_SKt+Urc<{=KMlU zyQgW8e?Y=D4vCgR2tZs<^wbCq#SP?UXew?dR%(Z~;#T6MZs;m*BX5Ka#hrwOj6dsh zpEpm;(2LlC&s%)y6Y$G?m9L$c?;&iSQ?KwIUsr02JI}RX<2RKVK;@0HOcbLc7Fk}l z;ckDYvZo`NM^ajtQQ0yoW-_bH9g#)lE_49O*YF+O@`?Tyyz-lV>*iwRaeRbX(Zgu` zLcBbP_KMxgkVyOm3lkj>OP1<5`>rYZRVNi3Lb`#D zFrfSe{39J1kyx|B!pJ{RcutGdI(jePXMgtR_mZ#vFSW{hJw7+7}E$$@UR7DE$ifD<% zk9;IuBel|O&b6vw6LJ#%mjDLh^?V57kc&&n( zfe~{$A*kefL*HtZL(N{2@b+&khJ?_p%Y&kd^K6mhv)LH&t7>NXu~PEJi()qm(1NUhijY}Oyx{{AM)OV@1W zDF{FS2YVN(9b>v-;LMy!Yry&a?TM4!!PIn1yibv5$?^)8NBP?j?6^MQyQ^)Kl)L2RkSWNC1)s X*pka<#FS65J@hZd5Hz?@{yq2yfN_(V diff --git a/venv/lib/python3.10/site-packages/_pytest/__pycache__/skipping.cpython-310.pyc b/venv/lib/python3.10/site-packages/_pytest/__pycache__/skipping.cpython-310.pyc index 1a3d489c1fc4167e7d11c5078ab1d89fa4706a74..dba24adb69eeff7bbae6894ad4037d6d0f7eed36 100644 GIT binary patch literal 8854 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 delta 4265 zcmb6cTWlN0ad+>*PRYkr^r&M z(23lS1~uAZf&LUhq@Ya^pnq;rpdU%wpZ*0!fda*S6a|WW6$qLF%}3l=omonj6r=^p z!_Cdk%+Aiv&SUwb$KF5RZpLCZf!_yrzft}3K|=n9gS|iGJ`Arn%n0G6NI2!prA4Z~ ztjN??D{AVi7j^YDiUxc&*Q|$%A&PT!*Q$q$VTBuj+eI7jHsvM^BE^VegaD5gqYAg& zSUp~hD?AK%qL@&)?I!D8#V&dIN;FQ1RvtVx9AgukMPl(gpaPXby_?KbK4o1 zWIG38g3ibII83;2--H8v0wzqT2?ezn$`_RUL4F^|PdY;#N}jBf;*^ts#R|7*M^+g> z$)|4Wu;3xZJG8@d!u;?hrRWGhsss-26FBA!aNLQ`kMsK#{|G4%Bi>1QhfO97Zu7`39nOcqYEgMc`(FI| zT)FC&JYQ5R{_0ivN9}5NpkHe?-2xMNh~7Z=QF}u_Mn~m4`hwYuA>A*NMt_0Ycf>f# zn3ZN@t~y_#FxdG)&=Uh*4PJTGkd(-O7@0G17=q0q&+*H?FT^N}-)Di=ba`MduJ{g^ zf;2G(Ah6E5<@z;Ve&~dJ(0u$f@pPan>*i+L|F7nbqcnY*h%Ycv;6$zPk3 zG=BSI(_qoP`@Q{{^&1-L!PN=~;_|n4Kb??&v2#-gkZ>4*QU(dWZBnvhWT~^|CE*;F zBax$hCqN+3tG-hY^m1RXi>E{YWlJyu08XpR|}y*O_=v{wgxv-iWmHO}b9s zq%CbDiZ~^&Wc^r+a2 zgW6L&YM05X>G!oDzttWJYMDQ6FEhYXojuIc|7QWvZ|v#^4gDJfyqkAzvNgM9^GCXt zh`4j_Og`ahbP%vYEqe|Ul4rIZ*8|jupJ_#Q{IVla$lm@i_9TsAv=peo&+1v3Nu1GN z8xd>r*+hSO;4vYZV%T}L>@Jmk$m!t&LarsAOiz9lC9I#{*`!gY%s8^UbW%* zPQw-3+z6=XGN>yOaHATJb2ju@vl!coGyU$M4zxS*kVS23Q7dfSL9J{ns#{E4m8Vk^P$fzMC2ip~*9ako-gHh>5s(L~f-rhg9?Zh>zM#-qO&AZH2G{dG|Ss_-G^xE85EpGpnO3@O#USO;7jNuEAV-A~7IlHWLZC;37%{D?`xU0nzy-nI!Rui^!86dA#o+vDKNZN^*NVbG2*Hb<^*#X5A~kgzyG0)*FJ6}C zv$+W%X&X8>Hz^bc4f39DFdpJi!`5^toG^Zsib?r)HcKzaUuM5fugFicS>w@HOm4|o z?m`<45e@?M$0g+u(T8K@l-*zub3iOuVjg)aAXF@1?FdZBr)s0x2@%|fijG2fHyV`o zj&js1st6#)Ufd2h42!)n0mC4@U3{EUr~6@l@;(BNhYn4$IPHOq!u*LcU5nEcwX`JF zfeM*)S2u(wPvj3m9$m@b%3x%P7=q&nRIYgPi+rvhcOZ@;P%Tx31$w2RiIehpZ%=#o zAn^=?^mZqOz{0j)f`YTO_;=u_L$#PrlQhNBU`}*PyoQQs5P7-TTv!%xJ90!po0*x} zchC%kFqkm3yf*>7p^kck`izrJ`BCpVgOkgI%7^=URlG{5>jaw-uY!ER7R&JI zWao9nJOux3XKMFwQBw796(#pq2`ibY1SRJs!HTeG1Qk|b{Z-R1IukU8}q_h z6a}*r2(N<<@f}p8Az8(R%GEu=f!zF;;=f*Aa9~1H`0&S%c=v-0yJ14qU~XbIrXV3~ zcuaVD@XMkIPrxv#2G&1yOMDk~!}T8(?aK~Os&3<024!?;%DM>%B^Pj&;g^1a2PXid*)uZ>VdF zy0NIc%eM$QMuJ48>AFq@ugu<&^0kV{Wp#5}`_Lz1*z(~B;($dtjbJR-a=y>0M0PE1cA^-pY diff --git a/venv/lib/python3.10/site-packages/_pytest/__pycache__/stash.cpython-310.pyc b/venv/lib/python3.10/site-packages/_pytest/__pycache__/stash.cpython-310.pyc index ad9d761d3bd03edd4c22e38e35d0cfd248aefc7d..541d4c8774a65abe82bc81a9d6e4e2eae7585f75 100644 GIT binary patch delta 1487 zcmZ`(-D@0G6rVfaJ0GjvZkuLJvYRv?UD2%+q!^?qDfm%Xk(5?O?Co^V&30fiD|coC z34s;kt3sH+z{fsIAAI*+eVtdI1fdjtDn5A5-GtO6y~F(OZ_YXQe9p|@^WV1XqcHR| zTz_2nBxwYP_11*td-x0*uvFV-5o5&lRNpotLvaJS8JUWk$gRjy+(K?gHgX%Bp&mKH z2KRtPF77qlYaeOgf%BaPj#zs^{>$mVPpt@oal8|=51*TL26b#{cz@l*^TV12!Fv-eKf z&^LS^E#(x-hx+{qoqQed+(vLUlex74vi~gupFYqumID%@MGUk^2NoHiM<%qv!t~9^ zf({lEAhTf+LZ~BiU#j)$ zW5u8T^wj8?@4dPvrFp_Cm*>Su#zRrrQWRsERc3#biSl3P$~wzwzKOQ4aU3K%XMNc~ z2_7?Zk!~He110hT0{Y@fmK2VhK2*`%kd&k z_XhT<{N8>021V!^at1d!OYjE4N&8hOpYvQ4Ng=j5zf=C@zKio*s_on~$(CZsiwIBj zm)xB7Fg*6~S@~;ibxl&WFRZ(r{m$((Dblzp#eAuJ?lsG0Z-2H;VNQ89(C5AKXOHrE z=KZ*`NH$l`G4wEf5L@v$EgqCl{aa}Nk6)}(@pAqgdn_14IxTou{t(Q!EA!E4ln!(> zK3>G>R=xZsxW4vEcLC>vQC7rBmUEt!t#HArj3g^)?wHEw;q3`EP+lfDRindsDl*O` z>ADDep&E4WNZ#-2vPrs22wi(R#1;wEB-M0I^yW)?B_%zcnyA^UW2(_KoT=N+)Zmo< z#I160#;r=`jC75B9i{sw@lXc1$xxdh)``AGKwrANLNG_LK(I`(La<73jo>YUlYS{i zFRc11X|6eSlztcHHBa{p&-5J6^=h6|H~Zb7@;Kib<7X&%ol6>JWf!|&C)u$4rr!Lb u!q&%Wimg}Gs+Q1;sl1;TwsPO!9>F*jw;08y{svm02aGP)z>77FM))5mh!*hx delta 1354 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 literal 3857 zcmZ`+-EJGl72eq$E|)8kl4x0$+B9Y3CMgSxbeaYQQm2Sx%dy%*H6j5;i5A;6XGATv z++}8#vPDogv3r>(2m;8Ld5JziA7O8M)kmm-+WpQfsXxn0?ChB{XJ*d-IZSIc-@^0H zR}Z5>!?ONOlk-mrlh5($e_;@oV6io1UB(Eu0oz^Mzz$%i>lm2Fyvwm(ic3Sc>l&OJ zmxo^0GjKVs4E?TeU@u-6R=ZULSK`{R-mNoBI_eJg`|;v%sk?-=h3>MbN?+6j-{Yb# z7N7HOL-N5ju_V12c3Cu@^JlEPBCg5m47Vb#gYx>9mRJ=xo?GI^GrPMg_>R?D`w!%? zS}f;(jC6*t_oq=7rAZhAuI>Ki(PnV(;pV+RY;AwJ4NNIXh13Atdug&4^$B_uDlHYD z@{ZIRdja~pQfFVuQL5<7(!H>^FDLaLeL6amqbHG;xjQmDb63f1tdbrhR%VZluZQ;@ zUi~SCsdWOazhK8~$_7l>!Vz4Qge%Iz`xd%2*oyECc0p7Ntgs+sZ96abWg=A=Px!`$ zelqbf&bjb)mMR%!YAhe*-W)yoV)JQ~HPfV-L6YWP7{y~Hn_(iFy)?<9WGtI|DjhcC zaJC#J{em`;PqStg4P}exZrCGj=4B!G!f~AC9O}ckFXPdqLcDC~gJ?8aJY&cp^4gq{ zz^n&b{RCV#vwd6}smoVB9%oI6F*5vu#IQg-&zWjBw}zuQ>P1<6)Ram1IF`D3c4xa~ z=U!iCeU*+!c_kDgHD1uN)dF;%m(0QsmRiH?u@sD~S4n7;EQRR?+$_GIV5 z-Oan5Lzx_Q;^=W_bd>F<$;a)FZg+H)$qkSkgng+y0al=gQ&(t@j`F1f7o1*>Ftqf0 z7<|T=eeUPJ+gZPFA@$Fr;NLSS7W*Nofew4Jzxm##TtzfyJsX&nhGOrb6!!Uu^ zD)*G^Bgz2PcV_PQs5pnIAJcwPZ!xUXZF7|ra>4&T1Xfd`@@&uc*(&3&6=^6h>mu~t zF8h6uk;EsAqDVK59&BNanXdod`i5l=VTAq6R;wg)v3|m{Qs26u3un6!bAhZbRg{dz zS<5Z>f0uYEnJ~}G`=JiAOy!Q2S>D*$-F)=ttsSJ3yGSU({jINlVCp95FAKdg^Mjd0 zdr=bU{o5eaRSZ?O#Hx%tD>nbH`XvxOycZr9v(NGB2?l9(nXtOHU=mgq4zeb)267=6 zt}M+k$RNPDhNdFR(i>FPp$SmC$R`U}^O1EIX4ExN2c{})S(Ej3YtI&oVhNZTKth@y1rAxSW)n*OW=dfzHBgtkwI3bKt1z7X*Oxn$&L=ntAzmYB_W@}4Cce> zvfMx0cR5G=WOi~djp<-i*kG87Xz$2yN#Uf)ja|n@>EMn@8B|1Gd9Kguc9r?8!6vt^ zc8seJr7nfqnYVkc!i}(*rQU}JFtq|0_8vI!g+&wBj)`ky|6)vC3bNpL!q_8?CCg~I+0V*L&V z$_`an#J4@C&TQL%?YmV}G5$YpPd>cb7H^#PMu9sG(GPG1g>M`&}08?$*Q+j_3 zKvQGrSq;{x*baKe*B)ND-mAu1W365?bgdh?n;lWgEN-Ve6e%2kP)wVemuLNI!B~AZ zYZq;q5hGu}h-`N!X ztY%tWj}mNzmfo~~g&s`1ac0R=W#rzS!T`S`UpF0f6@65dc}Rrw9|z3shQqI~u!h%I F_&=Fu$y@*c diff --git a/venv/lib/python3.10/site-packages/_pytest/__pycache__/subtests.cpython-310.pyc b/venv/lib/python3.10/site-packages/_pytest/__pycache__/subtests.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ebb595c795b073c7732bd904df0b8961f7018739 GIT binary patch literal 11908 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.10/site-packages/_pytest/__pycache__/terminal.cpython-310.pyc b/venv/lib/python3.10/site-packages/_pytest/__pycache__/terminal.cpython-310.pyc index 60683792ed5c1dfd95e758e9604bfc92a85b8559..0bd717299a531d7d0cfc45eaccce2a658a7d9252 100644 GIT binary patch literal 52402 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 ANB{r; literal 43420 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.10/site-packages/_pytest/__pycache__/threadexception.cpython-310.pyc b/venv/lib/python3.10/site-packages/_pytest/__pycache__/threadexception.cpython-310.pyc index c28d8bc664f3d466810fdc404de75fbd5883a8a3..c41fb0d42407550536b8a83a56486bd84a2194f3 100644 GIT binary patch literal 3672 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 diff --git a/venv/lib/python3.10/site-packages/_pytest/__pycache__/timing.cpython-310.pyc b/venv/lib/python3.10/site-packages/_pytest/__pycache__/timing.cpython-310.pyc index 987e09194699ddeb7ce882af410b465f4db8efed..a726a70314d8a896f36284d19c5efbb6df3acce9 100644 GIT binary patch literal 3821 zcmZ`+O>^7E8O9gCKuV%yH*u0Cvz3$7Y!xz2nl^Pk8GBSa^>nQ9*h)La1zK{K5+o2{ zcR{J*s3-aA|6uo~>EGzDz_q>ToJ%K{_IVeeNLdOrTI}w-*!SysKbS5p`4*mkuRe)B zZsARf#mB?qfo0kHUzi9hu!Jp~*d957W7FJ?-N41$i@lK-cxJDHc_pZrc{Q$%YC+A+ zYna!Ax|!E8uLe~!Z^Vs}ANaNH6|}H+_m@Odv|icYT4G5ozqG{isdH)v z%adx7l88vjK^CP68>Wh7(MYo4 zBr!X>>-&F}EJ`w&&*UVJWK6<}9dfemOm-vLm8E4}@SaD)r@d7|>l396zhYod1FGL-rPrEVyV z`*5^qiKUlb&=5BS!NCiBu}aR)YfloL@g$r6(`R^|%dwIgp4E&eu%V&hZ7{IGCR)OK zDeI>&>8{LRR%u8$HyX1;9>Y02F=s}SYxu@oOUJ}S6+TRdj2D>FW1hf^#28|X0Xo6@ z$L#q;XYkC9&cwZYWXgN@N-1}`=I|AJ*)Y>4!tnblCYg0=y|B*gEmgT!~)j3k2_AjBIJ-y;w&5jm#I3=y(o#Y zyfTbao~Z`D{87c!GU>2FLxqM5m5SLa?X6+BLz5dAa(@oays9L^B+2W)FMTi|gUueA z6X3{zn)qbaiIv%>fZHnvAaSO5pBBC}?!NG&j*@|7J2Tg?Bd*y%NyLiiLfZ;3p|af5 zSvu}G>OEYnmM~C!Xc-R|bRhBbnvD4v`1&Oln#{QDOjl-RENRNc{*Mb!R>`c__KCIc zWbVHAhQzn0x5`@>1pID$lg+}p+o_lf^Cg7gh=68bY&@aJBDp)s2I{Any)A5&>kzaa zNJiGSGhLp^T9CG&^DX+46rasW{7y5khhf4;G7NJ+42z_Mc`FS6I^l6isfD3P2iR^x zTt0}o)=~p2k&K0rFkcD7g+vvG>Rn>@5e+}1;W~_v14t@OMBnn9_Zyy9x0nCtd&{12 z&lLz>m%m9O(%c`GwvdGEhbMq9*DuJrtp;Xu$eSqyO}ahm3-qA@<46aly1_Ar_E#v|CpFb*)Rj?)z z7Sj=uo0fw#5qe`T(*)q&Wt>qEQSl+9mx;b8AE=L!Ho1?|K*ud)G#)S_Uk936$>(PCcL`u!YHGPs6{cIaF~(Rn$(auPbjXc({(rL-pky(|&kC zUA$Y?mxj27T65D>nxDW6@8$ke)aY%KHn{_r(NeQApLUCUTGVRJ4iYNMX@4K>0KCOB zHUN0~@}dC2Uzti{@+diq3KaDeraB0Ta*XXrXVHM#ld{B=ZAFw(_F;N2ox}pY!Vy2# zbnimXLg|3*To!G|R71%HVR(l?yu*q%>4L#F^%%fbf?MPCh;Bv8f%ar3ZF(rnZVE}1 zcThWdF1H_$EJv%3`pHuBQze&Rv7!>X0 zOCk6uG$hpBq01ryWrEz>LK8Ni(4Rdtck#bT5T;^yg0i-6o!Gyz94kb3{-UU$XSSmI zk&{2f>+R?p(PhAe4hjW{TqPx)Ne?_=_2jsy$s< z;6b|P&5>yq$J8vUCQe?`mNX^P8Eqe6Vt#p8Bm3|8<#A%ac1{qm5X61sUaUQ=y@hpU zwkF$js#}V*ptcnJ0ZT<@D5@c8)v4sQ5{T;8Sm(%Rz+0x~=a2`DO~0ZK>C9UhDX$o9 z{sfAq=B(wkk&SL%Hn2Ie)$t0?5DJRi|0T_SfdM4LfNU}+q13F?#%&trxsvRZH^`tx zaW~L1ZPHx3-_j@7CD08J9^(HZfqvBV9p7Dc6;Yrq*=0ffHItvIst+gh!vsZ{P^mtp zfg~|)#oVOkQ<(dBOy;Jant8QIQ^w(WA66eA-;IztNUBE~3|6t-XnP4=oEo_Q&mMXAXpnfZANY57G8C7HRY z3TdTz$&>jRpHG%!5_8jJyu|_%)nqOL*;vF3B3OWgpC;=q_W1ae{N(s}umD0HB%YX) p6Cb~lp@YrDV`1Q7=V1gwCIFnGGSC13 diff --git a/venv/lib/python3.10/site-packages/_pytest/__pycache__/tmpdir.cpython-310.pyc b/venv/lib/python3.10/site-packages/_pytest/__pycache__/tmpdir.cpython-310.pyc index 053541ebed6d604d86aefe93efce30f2d8661a4e..2e3d0a97b0a6e4afbe1102cd1aee607ec4041705 100644 GIT binary patch delta 4153 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 Xb=#aUi)L4@zer3&%>f)XE4}1DjxN2! delta 4342 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 CbmIyD literal 0 HcmV?d00001 diff --git a/venv/lib/python3.10/site-packages/_pytest/__pycache__/unittest.cpython-310.pyc b/venv/lib/python3.10/site-packages/_pytest/__pycache__/unittest.cpython-310.pyc index 4e6d543206c1067588d2199514ef3304cb700b2f..3effe458b9d2cd07a3f3b727ebf08f56b3cfd589 100644 GIT binary patch literal 17663 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

    jP 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 AW&i*H literal 11527 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 GM*jnEebJf# literal 3293 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 H@o4T}8ab1? diff --git a/venv/lib/python3.10/site-packages/_pytest/__pycache__/warnings.cpython-310.pyc b/venv/lib/python3.10/site-packages/_pytest/__pycache__/warnings.cpython-310.pyc index 583f113ff6f9d2134ced888876753d19fc91c0d6..dd78085aeb2616a5d1552912d5766a245136dc91 100644 GIT binary patch literal 4327 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 literal 4185 zcmbtX+io1k5uKiyogMCtONzSCmgI@-SY9XO+6bH=LSWdIb#rV=u^1~_!&r^Bo68~R zvhE&=Cb0zMNUw}Pz;Ge~dC^bg6Y>H5nx_ECOAO>8wxpcunWac3P6A{H(^K7DU0q#W zb*eUNHft7se|T~^`r{GH`V)2b|7i9tyy~~ivbe=rZ1svS>oN50*iM|D!)VNj-K5g1 z7}^Ex^*lpYKv#QJL$lcL`QY{9sifAcnXxM9darJ1A9SPFF!WU1Oj^B`p=r+NFn+dC>8Gd9C#`4QoX_PoVgqQd9q ztredi<;U*3_gL>3KQ7qXam<{+$`gDZE9Zs3_8h0@`CS`p+k5L=ev+Ru>z+Trb6T|a z@w~tT!*jCa`Q9$$XTDAOn%_E(}f7gAxuJD;U1(Gb+!D3nSZV!?Rzx=;#&LAqCRk>J~27BY#j z)rVq~Nk}5L_ahKttUag+iQ1DQ-;n75F1Gh0+QoYwuWF+iTMyuV+kh&Z6c!7U{T5gAB!012u|PU49MmTuOF)w-jqZf; zhUmvpDmqLyajr*}oWbWG%MhN3?s}+_g$*TScQcdgY7`Dc_u|_Z-tI!xJKZ?C-NiPm zS^A^J*I(-@7;*tD>$qjr?H3Ak_p};q>2@*JFDwn6=g6Ku8kgC4{3jA&Z)taPRixWz z%bWT5Y{7r_hXJTK7K5BfxdE?I`Pomji;WZ9z|zuyE@|?sgP6iQz^nd_=JWE}9_vQ_YyDmKQ9$}t) z-Fn?p`6g!Qxrvv?I?e6BB3LK_gE9o+Z($w`!hEnQ)WjWCFf5`{@F0RC!X;t%JcM)b zKzt?!8x*F&?Km5(FV<=Sez$IsTx!oli97YH;y&4XMJA#@x zm$Vngo8cBAh>!D&VJ5e)8hM%)HehnuF9Axsq?e?))5pKVqPjT<T{oft?e4*3*N86RPYDIB_;CG;v7@?LY&0iqs>x zDVkZM&p|`2+)va{J80yaw6UGv){TMYqw;wGvE`h9{XML#t z9t5b@(0I1XJmyZGZ)#PWHSoZ%oA%!t@cR~PIj-kErU-DEyo^Q59b4BaJ~t(bCn2?; z%dM42s{DZKlSh#2o9y@wh|jH@+@D+?PCZ67z3YBqYG2Am$TY|^Wu`&FgG}?>flMc4 z%wzch26R;w<=%lq+L)Of^6QJ}Pzg{(_?RKF;-K`m;lCX8D|7(Rq?Gkax_DQ1vEMqvHAg~quEDQfXrF$sT>RkpQT-wy;2zin;p~`xVCj^ZxP%?M3^_ z>(m_DaIUmLd21_3nrihvG5(U8n`m@Hgc8Mnky|8T^?@x9c<@`8Ej*Zf_K|5*1_~5e@fE?H`*t6kCIVr;!u*yN^%c# zhmy{{&wj=WOrrOHmSlV*7H`ruDl)Tg`3Btx;e15*v&Thm*0Ft))1TUo{n&HuZ@o(6 bo4JL?=@YC`J;7?_tY!Y diff --git a/venv/lib/python3.10/site-packages/_pytest/_argcomplete.py b/venv/lib/python3.10/site-packages/_pytest/_argcomplete.py index 6a80837..59426ef 100644 --- a/venv/lib/python3.10/site-packages/_pytest/_argcomplete.py +++ b/venv/lib/python3.10/site-packages/_pytest/_argcomplete.py @@ -61,13 +61,14 @@ If things do not work right away: 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 glob import glob from typing import Any -from typing import List -from typing import Optional class FastFilesCompleter: @@ -76,7 +77,7 @@ class FastFilesCompleter: def __init__(self, directories: bool = True) -> None: self.directories = directories - def __call__(self, prefix: str, **kwargs: Any) -> List[str]: + 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) @@ -103,7 +104,7 @@ if os.environ.get("_ARGCOMPLETE"): import argcomplete.completers except ImportError: sys.exit(-1) - filescompleter: Optional[FastFilesCompleter] = FastFilesCompleter() + filescompleter: FastFilesCompleter | None = FastFilesCompleter() def try_argcomplete(parser: argparse.ArgumentParser) -> None: argcomplete.autocomplete(parser, always_complete_options=False) diff --git a/venv/lib/python3.10/site-packages/_pytest/_code/__init__.py b/venv/lib/python3.10/site-packages/_pytest/_code/__init__.py index 511d0dd..7f67a2e 100644 --- a/venv/lib/python3.10/site-packages/_pytest/_code/__init__.py +++ b/venv/lib/python3.10/site-packages/_pytest/_code/__init__.py @@ -1,4 +1,7 @@ """Python inspection/code generation API.""" + +from __future__ import annotations + from .code import Code from .code import ExceptionInfo from .code import filter_traceback @@ -9,14 +12,15 @@ from .code import TracebackEntry from .source import getrawcode from .source import Source + __all__ = [ "Code", "ExceptionInfo", - "filter_traceback", "Frame", - "getfslineno", - "getrawcode", + "Source", "Traceback", "TracebackEntry", - "Source", + "filter_traceback", + "getfslineno", + "getrawcode", ] diff --git a/venv/lib/python3.10/site-packages/_pytest/_code/__pycache__/__init__.cpython-310.pyc b/venv/lib/python3.10/site-packages/_pytest/_code/__pycache__/__init__.cpython-310.pyc index 5de19e9359166e3865b22bfb239ba92a60953bef..590a0c7a2e05972d2f2ba286d8434a03c22bb31c 100644 GIT binary patch delta 358 zcmZ9{KTE_g7zXepO`E3wj=Nh_`~qG49xgh#y-O%fJr3Fi(sp)na4okuikslaaSAwz z-@`Xq4ou+TmxMedzx*|>Oj%|G$ItfmDA`(FJ<;g}*O~y)RI{EA3>YOk(!7rb4m>zU z#}Ef6=me5rffgWwQ*;VxutZCc!5KP(EI9A;p@1T|K$oyBDLD{bzEh}l^+4*)`dRP& z6Km@7W*p7j%ttd$>(}!=$88(?5IzbY(ue<(O?;$2a*Vrr5smEnrY^RiE xc<6X*ba#5AmpA7EmrQ1{Z5OVGa$nH&y;2{3nV86!%6?m=nt*wM$`ZR delta 328 zcmZ|KJqp4w6bJAm%}2l89K1nq;^gA?mLN?Lp<1Z82^~B^adCF@22LKNt9S%g@x2Hx zVgi4D8Qwq1Nj+rVWLeB`-Conxo%i{qW7|ER9RudL6E$B85S$#K0|>k&T7vXeXa&k! zqcv!6gEnBiLv#qCcT`8~7-H`joj}6blDXu}A$2O9vm)!SMgKf>0$+Pbu8-I+6!JaqVmx(7n{i#DOt*Y Gws-@{Svuzc diff --git a/venv/lib/python3.10/site-packages/_pytest/_code/__pycache__/code.cpython-310.pyc b/venv/lib/python3.10/site-packages/_pytest/_code/__pycache__/code.cpython-310.pyc index a39548ac3325abd2dee8b231f707b726d2e6e59c..af9334d092248e0c7797d541dcbee0b2296345e2 100644 GIT binary patch literal 46846 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 U{BBO~8S2Zydlbr=_`t6J7fjr8m;e9( literal 39556 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 P>;8bmLq+@`1tCF0!Z~x}S0#2e z=bky|%$YgQna_^@=ebThld%c>e);HpBRxRKpK!AMmxakG82&{{2q&C2i4%XRL)Dx) zOno&+Q(xWD)z@$g__C&XGvOpCuF;y-&7_l5xDL4ObZj*-V3KlD3O4~yJ86X{0M9rX zgfOk1vl$;~n=BZV}Q!C8L@wAuq(q67Yy!T~9IR0qvxs?lIF1IZY(B(?R}H6+(`izB>TiTFLcaTOyrW-g z_(6?E)|ZxoM!PlJM7(|56V3K42i%yswbb+w`AQ4;_3Z&d+=Ouoh7ax}9>KssP$&ds zlxv>Ob@4X|7;zu5b;G=s4NC?t?9Ltv^XlCPWG8+k#|gfIk{gW{+jG0zb*6j z;_gF0o~r5MII@*NaB|UDQdR(6Mfb4$i9I}U5=73!@G;I=R-zUyJ+8vxtB*}4|6(5< zCH^q50P#CBR7;L*uA4wi#St0PFnsKUrY2X@hhdjrq~FP@^PfY(!w6oGuVjX)BkyHK zDm&thVOH^mul#l9ccya4GxCSofdiX5B5f9&6DvY(I}=`@bG|7*%}#=c2jv&K2IZBm zLnpM@jy_zly=1oRP->JND0Cf0YzHgKkGjUekbiZJnz&SjvSh*;T(>2U<@zd^-cW;S zWMbOXs5ph6iaeb^(230U+@ejY3{G8H0AC z73ru}u(O)26;p*o)wHdG89NQ_lEZ~EICN4TFPxSug?-?U_X}f#r-8L?FXcYW@nxh> z%6}BbI))g6No3DATHIHSBTAmVTldr@yDhtPkwUptETg)nP$}~w>e{kL?ePlG?!fSK z02nPmqcovWnk)zPAdou~t4j2owmI#Hd(RXh7DW0M}SZ*5Ptl)7G)a zUXpH4xpNt0BHLeTHUecWWQ|I%&zXubeH+y=07V+d7#$Potv2^|*o6Vc;#1rI5s(Gw z0eFNEHlHA0KfA02EYt$Bs0Bl0F}_tVJs}xDHen>@HMmLl%af%(IxMf2_RyE*8>I?8 zBEMhSPp`}0mWJse`Dtk=BVL9rBK^*6^M)wPVy`o*0_G~J-xh(uGpnvvv*TcT@}1rT z=94b)tKR+jcR|A63qaSh5XX`xANP(te;Wvs0wW(KXMNuj_#%rEQ25GbtcQ&s+Kfel zG4;>OmGbKYZ{lUXPL>n!lCVMpo<}g&QtKp4xM?|F>FZ?HvZ^)W+sjg}Ni17oVzDc< z-iAx@)j4<`RN#4#T+9XVioF-cj|7k?ghnq}HsO(w1b+9u)tD1M8bVY2NbPBIy-Z-w zBAg-_Cf9nny-xk_g-QPhA@B|!1r;sYVxj5Z54F$;&3O$TN~x*HY%H|e!i#O!y}Mpr z9FqU63`4>7R8P^5}UT2s{h8= diff --git a/venv/lib/python3.10/site-packages/_pytest/_code/code.py b/venv/lib/python3.10/site-packages/_pytest/_code/code.py index 9b05133..add2a49 100644 --- a/venv/lib/python3.10/site-packages/_pytest/_code/code.py +++ b/venv/lib/python3.10/site-packages/_pytest/_code/code.py @@ -1,36 +1,37 @@ +# mypy: allow-untyped-defs +from __future__ import annotations + import ast +from collections.abc import Callable +from collections.abc import Iterable +from collections.abc import Mapping +from collections.abc import Sequence import dataclasses import inspect -import os -import re -import sys -import traceback from inspect import CO_VARARGS from inspect import CO_VARKEYWORDS from io import StringIO +import os from pathlib import Path +import re +import sys +from traceback import extract_tb +from traceback import format_exception from traceback import format_exception_only +from traceback import FrameSummary 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 Dict +from typing import Final +from typing import final from typing import Generic -from typing import Iterable -from typing import List -from typing import Mapping -from typing import Optional +from typing import Literal from typing import overload -from typing import Pattern -from typing import Sequence -from typing import Set -from typing import Tuple -from typing import Type -from typing import TYPE_CHECKING +from typing import SupportsIndex +from typing import TypeAlias from typing import TypeVar -from typing import Union import pluggy @@ -42,22 +43,19 @@ 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 final from _pytest.compat import get_real_func from _pytest.deprecated import check_ispytest from _pytest.pathlib import absolutepath from _pytest.pathlib import bestrelpath -if TYPE_CHECKING: - from typing_extensions import Final - from typing_extensions import Literal - from typing_extensions import SupportsIndex - _TracebackStyle = Literal["long", "short", "line", "no", "native", "value", "auto"] - -if sys.version_info[:2] < (3, 11): +if sys.version_info < (3, 11): from exceptiongroup import BaseExceptionGroup +TracebackStyle = Literal["long", "short", "line", "no", "native", "value", "auto"] + +EXCEPTION_OR_MORE = type[BaseException] | tuple[type[BaseException], ...] + class Code: """Wrapper around Python code objects.""" @@ -68,7 +66,7 @@ class Code: self.raw = obj @classmethod - def from_function(cls, obj: object) -> "Code": + def from_function(cls, obj: object) -> Code: return cls(getrawcode(obj)) def __eq__(self, other): @@ -86,7 +84,7 @@ class Code: return self.raw.co_name @property - def path(self) -> Union[Path, str]: + 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: @@ -103,17 +101,17 @@ class Code: return self.raw.co_filename @property - def fullsource(self) -> Optional["Source"]: + 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": + 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, ...]: + 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 @@ -142,11 +140,11 @@ class Frame: return self.raw.f_lineno - 1 @property - def f_globals(self) -> Dict[str, Any]: + def f_globals(self) -> dict[str, Any]: return self.raw.f_globals @property - def f_locals(self) -> Dict[str, Any]: + def f_locals(self) -> dict[str, Any]: return self.raw.f_locals @property @@ -154,7 +152,7 @@ class Frame: return Code(self.raw.f_code) @property - def statement(self) -> "Source": + def statement(self) -> Source: """Statement this frame is at.""" if self.code.fullsource is None: return Source("") @@ -198,20 +196,59 @@ class TracebackEntry: def __init__( self, rawentry: TracebackType, - repr_style: Optional['Literal["short", "long"]'] = None, + repr_style: Literal["short", "long"] | None = None, ) -> None: - self._rawentry: "Final" = rawentry - self._repr_style: "Final" = repr_style + self._rawentry: Final = rawentry + self._repr_style: Final = repr_style def with_repr_style( - self, repr_style: Optional['Literal["short", "long"]'] - ) -> "TracebackEntry": + 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 + def get_python_framesummary(self) -> FrameSummary: + # Python's built-in traceback module implements all the nitty gritty + # details to get column numbers of out frames. + stack_summary = extract_tb(self._rawentry, limit=1) + return stack_summary[0] + + # Column and end line numbers introduced in python 3.11 + if sys.version_info < (3, 11): + + @property + def end_lineno_relative(self) -> int | None: + return None + + @property + def colno(self) -> int | None: + return None + + @property + def end_colno(self) -> int | None: + return None + else: + + @property + def end_lineno_relative(self) -> int | None: + frame_summary = self.get_python_framesummary() + if frame_summary.end_lineno is None: # pragma: no cover + return None + return frame_summary.end_lineno - 1 - self.frame.code.firstlineno + + @property + def colno(self) -> int | None: + """Starting byte offset of the expression in the traceback entry.""" + return self.get_python_framesummary().colno + + @property + def end_colno(self) -> int | None: + """Ending byte offset of the expression in the traceback entry.""" + return self.get_python_framesummary().end_colno + @property def frame(self) -> Frame: return Frame(self._rawentry.tb_frame) @@ -221,22 +258,22 @@ class TracebackEntry: return self.lineno - self.frame.code.firstlineno def __repr__(self) -> str: - return "" % (self.frame.code.path, self.lineno + 1) + return f"" @property - def statement(self) -> "Source": + 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) -> Union[Path, str]: + def path(self) -> Path | str: """Path to the source code.""" return self.frame.code.path @property - def locals(self) -> Dict[str, Any]: + def locals(self) -> dict[str, Any]: """Locals of underlying frame.""" return self.frame.f_locals @@ -244,8 +281,8 @@ class TracebackEntry: return self.frame.code.firstlineno def getsource( - self, astcache: Optional[Dict[Union[str, Path], ast.AST]] = None - ) -> Optional["Source"]: + 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 @@ -271,7 +308,7 @@ class TracebackEntry: source = property(getsource) - def ishidden(self, excinfo: Optional["ExceptionInfo[BaseException]"]) -> bool: + def ishidden(self, excinfo: ExceptionInfo[BaseException] | None) -> bool: """Return True if the current frame has a var __tracebackhide__ resolving to True. @@ -280,9 +317,7 @@ class TracebackEntry: Mostly for internal use. """ - tbh: Union[ - bool, Callable[[Optional[ExceptionInfo[BaseException]]], bool] - ] = False + 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 @@ -309,12 +344,7 @@ class TracebackEntry: # 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, - ) + return f" File '{self.path}':{self.lineno + 1} in {name}\n {line}\n" @property def name(self) -> str: @@ -322,18 +352,18 @@ class TracebackEntry: return self.frame.code.raw.co_name -class Traceback(List[TracebackEntry]): +class Traceback(list[TracebackEntry]): """Traceback objects encapsulate and offer higher level access to Traceback entries.""" def __init__( self, - tb: Union[TracebackType, Iterable[TracebackEntry]], + tb: TracebackType | Iterable[TracebackEntry], ) -> None: """Initialize from given python traceback object and ExceptionInfo.""" if isinstance(tb, TracebackType): def f(cur: TracebackType) -> Iterable[TracebackEntry]: - cur_: Optional[TracebackType] = cur + cur_: TracebackType | None = cur while cur_ is not None: yield TracebackEntry(cur_) cur_ = cur_.tb_next @@ -344,11 +374,11 @@ class Traceback(List[TracebackEntry]): def cut( self, - path: Optional[Union["os.PathLike[str]", str]] = None, - lineno: Optional[int] = None, - firstlineno: Optional[int] = None, - excludepath: Optional["os.PathLike[str]"] = None, - ) -> "Traceback": + 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 @@ -379,16 +409,12 @@ class Traceback(List[TracebackEntry]): return self @overload - def __getitem__(self, key: "SupportsIndex") -> TracebackEntry: - ... + def __getitem__(self, key: SupportsIndex) -> TracebackEntry: ... @overload - def __getitem__(self, key: slice) -> "Traceback": - ... + def __getitem__(self, key: slice) -> Traceback: ... - def __getitem__( - self, key: Union["SupportsIndex", slice] - ) -> Union[TracebackEntry, "Traceback"]: + def __getitem__(self, key: SupportsIndex | slice) -> TracebackEntry | Traceback: if isinstance(key, slice): return self.__class__(super().__getitem__(key)) else: @@ -396,12 +422,9 @@ class Traceback(List[TracebackEntry]): def filter( self, - # TODO(py38): change to positional only. - _excinfo_or_fn: Union[ - "ExceptionInfo[BaseException]", - Callable[[TracebackEntry], bool], - ], - ) -> "Traceback": + 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 @@ -411,34 +434,60 @@ class Traceback(List[TracebackEntry]): ``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 + if isinstance(excinfo_or_fn, ExceptionInfo): + fn = lambda x: not x.ishidden(excinfo_or_fn) # noqa: E731 else: - fn = _excinfo_or_fn + fn = excinfo_or_fn return Traceback(filter(fn, self)) - def recursionindex(self) -> Optional[int]: + 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]]] = {} + 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 - # print "checking for recursion at", key values = cache.setdefault(key, []) + # Since Python 3.13 f_locals is a proxy, freeze it. + loc = dict(entry.frame.f_locals) if values: - f = entry.frame - loc = f.f_locals for otherloc in values: if otherloc == loc: return i - values.append(entry.frame.f_locals) + values.append(loc) return None +def stringify_exception( + exc: BaseException, include_subexception_msg: bool = True +) -> str: + try: + notes = getattr(exc, "__notes__", []) + except KeyError: + # Workaround for https://github.com/python/cpython/issues/98778 on + # 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: # pragma: no cover + # exception not related to above bug, reraise + raise + if not include_subexception_msg and isinstance(exc, BaseExceptionGroup): + message = exc.message + else: + message = str(exc) + + return "\n".join( + [ + message, + *notes, + ] + ) + + E = TypeVar("E", bound=BaseException, covariant=True) @@ -449,15 +498,15 @@ class ExceptionInfo(Generic[E]): _assert_start_repr: ClassVar = "AssertionError('assert " - _excinfo: Optional[Tuple[Type["E"], "E", TracebackType]] + _excinfo: tuple[type[E], E, TracebackType] | None _striptext: str - _traceback: Optional[Traceback] + _traceback: Traceback | None def __init__( self, - excinfo: Optional[Tuple[Type["E"], "E", TracebackType]], + excinfo: tuple[type[E], E, TracebackType] | None, striptext: str = "", - traceback: Optional[Traceback] = None, + traceback: Traceback | None = None, *, _ispytest: bool = False, ) -> None: @@ -473,8 +522,8 @@ class ExceptionInfo(Generic[E]): # 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: Optional[str] = None, - ) -> "ExceptionInfo[E]": + exprinfo: str | None = None, + ) -> ExceptionInfo[E]: """Return an ExceptionInfo for an existing exception. The exception must have a non-``None`` ``__traceback__`` attribute, @@ -489,18 +538,19 @@ class ExceptionInfo(Generic[E]): .. versionadded:: 7.4 """ - assert ( - exception.__traceback__ - ), "Exceptions passed to ExcInfo.from_exception(...) must have a non-None __traceback__." + 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: Optional[str] = None, - ) -> "ExceptionInfo[E]": + 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): @@ -513,9 +563,7 @@ class ExceptionInfo(Generic[E]): return cls(exc_info, _striptext, _ispytest=True) @classmethod - def from_current( - cls, exprinfo: Optional[str] = None - ) -> "ExceptionInfo[BaseException]": + def from_current(cls, exprinfo: str | None = None) -> ExceptionInfo[BaseException]: """Return an ExceptionInfo matching the current traceback. .. warning:: @@ -535,45 +583,45 @@ class ExceptionInfo(Generic[E]): return ExceptionInfo.from_exc_info(exc_info, exprinfo) @classmethod - def for_later(cls) -> "ExceptionInfo[E]": + 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: + 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]: + def type(self) -> type[E]: """The exception class.""" - assert ( - self._excinfo is not None - ), ".type can only be used after the context manager exits" + 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" + 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" + 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" + assert self._excinfo is not None, ( + ".typename can only be used after the context manager exits" + ) return self.type.__name__ @property @@ -590,9 +638,7 @@ class ExceptionInfo(Generic[E]): def __repr__(self) -> str: if self._excinfo is None: return "" - return "<{} {} tblen={}>".format( - self.__class__.__name__, saferepr(self._excinfo[1]), len(self.traceback) - ) + 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. @@ -602,6 +648,23 @@ class ExceptionInfo(Generic[E]): representation is returned (so 'AssertionError: ' is removed from the beginning). """ + + def _get_single_subexc( + eg: BaseExceptionGroup[BaseException], + ) -> BaseException | None: + if len(eg.exceptions) != 1: + return None + if isinstance(e := eg.exceptions[0], BaseExceptionGroup): + return _get_single_subexc(e) + return e + + if ( + tryshort + and isinstance(self.value, BaseExceptionGroup) + and (subexc := _get_single_subexc(self.value)) is not None + ): + return f"{subexc!r} [single exception in {type(self.value).__name__}]" + lines = format_exception_only(self.type, self.value) text = "".join(lines) text = text.rstrip() @@ -610,16 +673,14 @@ class ExceptionInfo(Generic[E]): text = text[len(self._striptext) :] return text - def errisinstance( - self, exc: Union[Type[BaseException], Tuple[Type[BaseException], ...]] - ) -> bool: + 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) -> Optional["ReprFileLocation"]: + 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): @@ -633,15 +694,14 @@ class ExceptionInfo(Generic[E]): def getrepr( self, showlocals: bool = False, - style: "_TracebackStyle" = "long", + style: TracebackStyle = "long", abspath: bool = False, - tbfilter: Union[ - bool, Callable[["ExceptionInfo[BaseException]"], Traceback] - ] = True, + tbfilter: bool | Callable[[ExceptionInfo[BaseException]], Traceback] = True, funcargs: bool = False, truncate_locals: bool = True, + truncate_args: bool = True, chain: bool = True, - ) -> Union["ReprExceptionInfo", "ExceptionChainRepr"]: + ) -> ReprExceptionInfo | ExceptionChainRepr: """Return str()able representation of this exception info. :param bool showlocals: @@ -670,6 +730,9 @@ class ExceptionInfo(Generic[E]): :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. @@ -680,7 +743,7 @@ class ExceptionInfo(Generic[E]): if style == "native": return ReprExceptionInfo( reprtraceback=ReprTracebackNative( - traceback.format_exception( + format_exception( self.type, self.value, self.traceback[0]._rawentry if self.traceback else None, @@ -696,25 +759,108 @@ class ExceptionInfo(Generic[E]): tbfilter=tbfilter, funcargs=funcargs, truncate_locals=truncate_locals, + truncate_args=truncate_args, chain=chain, ) return fmt.repr_excinfo(self) - def match(self, regexp: Union[str, Pattern[str]]) -> "Literal[True]": + def match(self, regexp: str | re.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 = str(self.value) - msg = f"Regex pattern did not match.\n Regex: {regexp!r}\n Input: {value!r}" + value = stringify_exception(self.value) + msg = ( + f"Regex pattern did not match.\n" + f" Expected regex: {regexp!r}\n" + f" Actual message: {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 | re.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 = 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 | re.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 | 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`. + + :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 + + .. warning:: + This helper makes it easy to check for the presence of specific exceptions, + but it is very bad for checking that the group does *not* contain + *any other exceptions*. + You should instead consider using :class:`pytest.RaisesGroup` + + """ + 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) + + +# Type alias for the `tbfilter` setting: +# bool: If True, it should be filtered using Traceback.filter() +# callable: A callable that takes an ExceptionInfo and returns the filtered traceback. +TracebackFilter: TypeAlias = bool | Callable[[ExceptionInfo[BaseException]], Traceback] + @dataclasses.dataclass class FormattedExcinfo: @@ -725,17 +871,18 @@ class FormattedExcinfo: fail_marker: ClassVar = "E" showlocals: bool = False - style: "_TracebackStyle" = "long" + style: TracebackStyle = "long" abspath: bool = True - tbfilter: Union[bool, Callable[[ExceptionInfo[BaseException]], Traceback]] = True + tbfilter: TracebackFilter = True funcargs: bool = False truncate_locals: bool = True + truncate_args: bool = True chain: bool = True - astcache: Dict[Union[str, Path], ast.AST] = dataclasses.field( + astcache: dict[str | Path, ast.AST] = dataclasses.field( default_factory=dict, init=False, repr=False ) - def _getindent(self, source: "Source") -> int: + def _getindent(self, source: Source) -> int: # Figure out indent for the given source. try: s = str(source.getstatement(len(source) - 1)) @@ -750,27 +897,34 @@ class FormattedExcinfo: return 0 return 4 + (len(s) - len(s.lstrip())) - def _getentrysource(self, entry: TracebackEntry) -> Optional["Source"]: + 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) -> Optional["ReprFuncArgs"]: + def repr_args(self, entry: TracebackEntry) -> ReprFuncArgs | None: if self.funcargs: args = [] for argname, argvalue in entry.frame.getargs(var=True): - args.append((argname, saferepr(argvalue))) + 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: Optional["Source"], + source: Source | None, line_index: int = -1, - excinfo: Optional[ExceptionInfo[BaseException]] = None, + excinfo: ExceptionInfo[BaseException] | None = None, short: bool = False, - ) -> List[str]: + end_line_index: int | None = None, + colno: int | None = None, + end_colno: int | None = None, + ) -> list[str]: """Return formatted and marked up source lines.""" lines = [] if source is not None and line_index < 0: @@ -783,10 +937,30 @@ class FormattedExcinfo: space_prefix = " " if short: lines.append(space_prefix + source.lines[line_index].strip()) + lines.extend( + self.get_highlight_arrows_for_line( + raw_line=source.raw_lines[line_index], + line=source.lines[line_index].strip(), + lineno=line_index, + end_lineno=end_line_index, + colno=colno, + end_colno=end_colno, + ) + ) else: for line in source.lines[:line_index]: lines.append(space_prefix + line) lines.append(self.flow_marker + " " + source.lines[line_index]) + lines.extend( + self.get_highlight_arrows_for_line( + raw_line=source.raw_lines[line_index], + line=source.lines[line_index], + lineno=line_index, + end_lineno=end_line_index, + colno=colno, + end_colno=end_colno, + ) + ) for line in source.lines[line_index + 1 :]: lines.append(space_prefix + line) if excinfo is not None: @@ -794,12 +968,49 @@ class FormattedExcinfo: lines.extend(self.get_exconly(excinfo, indent=indent, markall=True)) return lines + def get_highlight_arrows_for_line( + self, + line: str, + raw_line: str, + lineno: int | None, + end_lineno: int | None, + colno: int | None, + end_colno: int | None, + ) -> list[str]: + """Return characters highlighting a source line. + + Example with colno and end_colno pointing to the bar expression: + "foo() + bar()" + returns " ^^^^^" + """ + if lineno != end_lineno: + # Don't handle expressions that span multiple lines. + return [] + if colno is None or end_colno is None: + # Can't do anything without column information. + return [] + + num_stripped_chars = len(raw_line) - len(line) + + start_char_offset = _byte_offset_to_character_offset(raw_line, colno) + end_char_offset = _byte_offset_to_character_offset(raw_line, end_colno) + num_carets = end_char_offset - start_char_offset + # If the highlight would span the whole line, it is redundant, don't + # show it. + if num_carets >= len(line.strip()): + return [] + + highlights = " " + highlights += " " * (start_char_offset - num_stripped_chars + 1) + highlights += "^" * num_carets + return [highlights] + def get_exconly( self, excinfo: ExceptionInfo[BaseException], indent: int = 4, markall: bool = False, - ) -> List[str]: + ) -> list[str]: lines = [] indentstr = " " * indent # Get the real exception information out. @@ -811,7 +1022,7 @@ class FormattedExcinfo: failindent = indentstr return lines - def repr_locals(self, locals: Mapping[str, object]) -> Optional["ReprLocals"]: + def repr_locals(self, locals: Mapping[str, object]) -> ReprLocals | None: if self.showlocals: lines = [] keys = [loc for loc in locals if loc[0] != "@"] @@ -839,10 +1050,10 @@ class FormattedExcinfo: def repr_traceback_entry( self, - entry: Optional[TracebackEntry], - excinfo: Optional[ExceptionInfo[BaseException]] = None, - ) -> "ReprEntry": - lines: List[str] = [] + 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 @@ -853,16 +1064,28 @@ class FormattedExcinfo: if source is None: source = Source("???") line_index = 0 + end_line_index, colno, end_colno = None, None, None else: - line_index = entry.lineno - entry.getfirstlinesource() + line_index = entry.relline + end_line_index = entry.end_lineno_relative + colno = entry.colno + end_colno = entry.end_colno short = style == "short" reprargs = self.repr_args(entry) if not short else None - s = self.get_source(source, line_index, excinfo, short=short) + s = self.get_source( + source=source, + line_index=line_index, + excinfo=excinfo, + short=short, + end_line_index=end_line_index, + colno=colno, + end_colno=end_colno, + ) lines.extend(s) if short: - message = "in %s" % (entry.name) + message = f"in {entry.name}" else: - message = excinfo and excinfo.typename or "" + message = (excinfo and excinfo.typename) or "" entry_path = entry.path path = self._makepath(entry_path) reprfileloc = ReprFileLocation(path, entry.lineno + 1, message) @@ -877,7 +1100,7 @@ class FormattedExcinfo: lines.extend(self.get_exconly(excinfo, indent=4)) return ReprEntry(lines, None, None, None, style) - def _makepath(self, path: Union[Path, str]) -> str: + def _makepath(self, path: Path | str) -> str: if not self.abspath and isinstance(path, Path): try: np = bestrelpath(Path.cwd(), path) @@ -887,12 +1110,8 @@ class FormattedExcinfo: 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) + def repr_traceback(self, excinfo: ExceptionInfo[BaseException]) -> ReprTraceback: + traceback = filter_excinfo_traceback(self.tbfilter, excinfo) if isinstance(excinfo.value, RecursionError): traceback, extraline = self._truncate_recursive_traceback(traceback) @@ -918,7 +1137,7 @@ class FormattedExcinfo: def _truncate_recursive_traceback( self, traceback: Traceback - ) -> Tuple[Traceback, Optional[str]]: + ) -> tuple[Traceback, str | None]: """Truncate the given recursive traceback trying to find the starting point of the recursion. @@ -935,16 +1154,11 @@ class FormattedExcinfo: recursionindex = traceback.recursionindex() except Exception as e: max_frames = 10 - extraline: Optional[str] = ( + 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" - " {exc_type}: {exc_msg}\n" - " Displaying first and last {max_frames} stack frames out of {total}." - ).format( - exc_type=type(e).__name__, - exc_msg=str(e), - max_frames=max_frames, - total=len(traceback), + 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. @@ -958,16 +1172,12 @@ class FormattedExcinfo: return traceback, extraline - def repr_excinfo( - self, excinfo: ExceptionInfo[BaseException] - ) -> "ExceptionChainRepr": - repr_chain: List[ - Tuple[ReprTraceback, Optional[ReprFileLocation], Optional[str]] - ] = [] - e: Optional[BaseException] = excinfo.value - excinfo_: Optional[ExceptionInfo[BaseException]] = excinfo + 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() + seen: set[int] = set() while e is not None and id(e) not in seen: seen.add(id(e)) @@ -975,14 +1185,15 @@ class FormattedExcinfo: # 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 + reprtraceback: ReprTraceback | ReprTracebackNative if isinstance(e, BaseExceptionGroup): - reprtraceback: Union[ - ReprTracebackNative, ReprTraceback - ] = ReprTracebackNative( - traceback.format_exception( - type(excinfo_.value), - excinfo_.value, - excinfo_.traceback[0]._rawentry, + # don't filter any sub-exceptions since they shouldn't have any internal frames + traceback = filter_excinfo_traceback(self.tbfilter, excinfo) + reprtraceback = ReprTracebackNative( + format_exception( + type(excinfo.value), + excinfo.value, + traceback[0]._rawentry, ) ) else: @@ -991,9 +1202,7 @@ class FormattedExcinfo: 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) - ) + reprtraceback = ReprTracebackNative(format_exception(type(e), e, None)) reprcrash = None repr_chain += [(reprtraceback, reprcrash, descr)] @@ -1034,9 +1243,9 @@ class TerminalRepr: @dataclasses.dataclass(eq=False) class ExceptionRepr(TerminalRepr): # Provided by subclasses. - reprtraceback: "ReprTraceback" - reprcrash: Optional["ReprFileLocation"] - sections: List[Tuple[str, str, str]] = dataclasses.field( + reprtraceback: ReprTraceback + reprcrash: ReprFileLocation | None + sections: list[tuple[str, str, str]] = dataclasses.field( init=False, default_factory=list ) @@ -1051,13 +1260,11 @@ class ExceptionRepr(TerminalRepr): @dataclasses.dataclass(eq=False) class ExceptionChainRepr(ExceptionRepr): - chain: Sequence[Tuple["ReprTraceback", Optional["ReprFileLocation"], Optional[str]]] + chain: Sequence[tuple[ReprTraceback, ReprFileLocation | None, str | None]] def __init__( self, - chain: Sequence[ - Tuple["ReprTraceback", Optional["ReprFileLocation"], Optional[str]] - ], + chain: Sequence[tuple[ReprTraceback, ReprFileLocation | None, str | None]], ) -> None: # reprcrash and reprtraceback of the outermost (the newest) exception # in the chain. @@ -1078,8 +1285,8 @@ class ExceptionChainRepr(ExceptionRepr): @dataclasses.dataclass(eq=False) class ReprExceptionInfo(ExceptionRepr): - reprtraceback: "ReprTraceback" - reprcrash: Optional["ReprFileLocation"] + reprtraceback: ReprTraceback + reprcrash: ReprFileLocation | None def toterminal(self, tw: TerminalWriter) -> None: self.reprtraceback.toterminal(tw) @@ -1088,9 +1295,9 @@ class ReprExceptionInfo(ExceptionRepr): @dataclasses.dataclass(eq=False) class ReprTraceback(TerminalRepr): - reprentries: Sequence[Union["ReprEntry", "ReprEntryNative"]] - extraline: Optional[str] - style: "_TracebackStyle" + reprentries: Sequence[ReprEntry | ReprEntryNative] + extraline: str | None + style: TracebackStyle entrysep: ClassVar = "_ " @@ -1102,10 +1309,8 @@ class ReprTraceback(TerminalRepr): 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" + if entry.style == "long" or ( + entry.style == "short" and next_entry.style == "long" ): tw.sep(self.entrysep) @@ -1124,7 +1329,7 @@ class ReprTracebackNative(ReprTraceback): class ReprEntryNative(TerminalRepr): lines: Sequence[str] - style: ClassVar["_TracebackStyle"] = "native" + style: ClassVar[TracebackStyle] = "native" def toterminal(self, tw: TerminalWriter) -> None: tw.write("".join(self.lines)) @@ -1133,10 +1338,10 @@ class ReprEntryNative(TerminalRepr): @dataclasses.dataclass(eq=False) class ReprEntry(TerminalRepr): lines: Sequence[str] - reprfuncargs: Optional["ReprFuncArgs"] - reprlocals: Optional["ReprLocals"] - reprfileloc: Optional["ReprFileLocation"] - style: "_TracebackStyle" + 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. @@ -1151,18 +1356,26 @@ class ReprEntry(TerminalRepr): 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] = [] + 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: @@ -1170,11 +1383,8 @@ class ReprEntry(TerminalRepr): failure_lines.extend(self.lines[index:]) break else: - if self.style == "value": - source_lines.append(line) - else: - indents.append(line[:indent_size]) - source_lines.append(line[indent_size:]) + indents.append(line[:indent_size]) + source_lines.append(line[indent_size:]) tw._write_source(source_lines, indents) @@ -1241,7 +1451,7 @@ class ReprLocals(TerminalRepr): @dataclasses.dataclass(eq=False) class ReprFuncArgs(TerminalRepr): - args: Sequence[Tuple[str, object]] + args: Sequence[tuple[str, object]] def toterminal(self, tw: TerminalWriter) -> None: if self.args: @@ -1262,7 +1472,7 @@ class ReprFuncArgs(TerminalRepr): tw.line("") -def getfslineno(obj: object) -> Tuple[Union[str, Path], int]: +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). @@ -1274,7 +1484,7 @@ def getfslineno(obj: object) -> Tuple[Union[str, Path], int]: # 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 # type: ignore[attr-defined] + obj = obj.place_as try: code = Code.from_function(obj) @@ -1284,7 +1494,7 @@ def getfslineno(obj: object) -> Tuple[Union[str, Path], int]: except TypeError: return "", -1 - fspath = fn and absolutepath(fn) or "" + fspath = (fn and absolutepath(fn)) or "" lineno = -1 if fspath: try: @@ -1296,6 +1506,12 @@ def getfslineno(obj: object) -> Tuple[Union[str, Path], int]: return code.path, code.firstlineno +def _byte_offset_to_character_offset(str, offset): + """Converts a byte based offset in a string to a code-point.""" + as_utf8 = str.encode("utf-8") + return len(as_utf8[:offset].decode("utf-8", errors="replace")) + + # 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 @@ -1335,3 +1551,15 @@ def filter_traceback(entry: TracebackEntry) -> bool: return False return True + + +def filter_excinfo_traceback( + tbfilter: TracebackFilter, excinfo: ExceptionInfo[BaseException] +) -> Traceback: + """Filter the exception traceback in ``excinfo`` according to ``tbfilter``.""" + if callable(tbfilter): + return tbfilter(excinfo) + elif tbfilter: + return excinfo.traceback.filter(excinfo) + else: + return excinfo.traceback diff --git a/venv/lib/python3.10/site-packages/_pytest/_code/source.py b/venv/lib/python3.10/site-packages/_pytest/_code/source.py index 208cfb8..99c242d 100644 --- a/venv/lib/python3.10/site-packages/_pytest/_code/source.py +++ b/venv/lib/python3.10/site-packages/_pytest/_code/source.py @@ -1,17 +1,16 @@ +# mypy: allow-untyped-defs +from __future__ import annotations + import ast +from bisect import bisect_right +from collections.abc import Iterable +from collections.abc import Iterator import inspect import textwrap import tokenize import types -import warnings -from bisect import bisect_right -from typing import Iterable -from typing import Iterator -from typing import List -from typing import Optional from typing import overload -from typing import Tuple -from typing import Union +import warnings class Source: @@ -22,13 +21,17 @@ class Source: def __init__(self, obj: object = None) -> None: if not obj: - self.lines: List[str] = [] + self.lines: list[str] = [] + self.raw_lines: list[str] = [] elif isinstance(obj, Source): self.lines = obj.lines - elif isinstance(obj, (tuple, list)): + self.raw_lines = obj.raw_lines + elif isinstance(obj, tuple | list): self.lines = deindent(x.rstrip("\n") for x in obj) + self.raw_lines = list(x.rstrip("\n") for x in obj) elif isinstance(obj, str): self.lines = deindent(obj.split("\n")) + self.raw_lines = obj.split("\n") else: try: rawcode = getrawcode(obj) @@ -36,6 +39,7 @@ class Source: except TypeError: src = inspect.getsource(obj) # type: ignore[arg-type] self.lines = deindent(src.split("\n")) + self.raw_lines = src.split("\n") def __eq__(self, other: object) -> bool: if not isinstance(other, Source): @@ -46,14 +50,12 @@ class Source: __hash__ = None # type: ignore @overload - def __getitem__(self, key: int) -> str: - ... + def __getitem__(self, key: int) -> str: ... @overload - def __getitem__(self, key: slice) -> "Source": - ... + def __getitem__(self, key: slice) -> Source: ... - def __getitem__(self, key: Union[int, slice]) -> Union[str, "Source"]: + def __getitem__(self, key: int | slice) -> str | Source: if isinstance(key, int): return self.lines[key] else: @@ -61,6 +63,7 @@ class Source: raise IndexError("cannot slice a Source with a step") newsource = Source() newsource.lines = self.lines[key.start : key.stop] + newsource.raw_lines = self.raw_lines[key.start : key.stop] return newsource def __iter__(self) -> Iterator[str]: @@ -69,7 +72,7 @@ class Source: def __len__(self) -> int: return len(self.lines) - def strip(self) -> "Source": + 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(): @@ -77,34 +80,37 @@ class Source: while end > start and not self.lines[end - 1].strip(): end -= 1 source = Source() + source.raw_lines = self.raw_lines source.lines[:] = self.lines[start:end] return source - def indent(self, indent: str = " " * 4) -> "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.raw_lines = self.raw_lines newsource.lines = [(indent + line) for line in self.lines] return newsource - def getstatement(self, lineno: int) -> "Source": + 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]: + 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) + _ast, start, end = getstatementrange_ast(lineno, self) return start, end - def deindent(self) -> "Source": + def deindent(self) -> Source: """Return a new Source object deindented.""" newsource = Source() newsource.lines[:] = deindent(self.lines) + newsource.raw_lines = self.raw_lines return newsource def __str__(self) -> str: @@ -116,13 +122,14 @@ class Source: # -def findsource(obj) -> Tuple[Optional[Source], int]: +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] + source.raw_lines = sourcelines return source, lineno @@ -139,24 +146,23 @@ def getrawcode(obj: object, trycall: bool = True) -> types.CodeType: raise TypeError(f"could not get code object for {obj!r}") -def deindent(lines: Iterable[str]) -> List[str]: +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, Optional[int]]: +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] = [] + values: list[int] = [] for x in ast.walk(node): - if isinstance(x, (ast.stmt, ast.ExceptHandler)): - # Before Python 3.8, the lineno of a decorated class or function pointed at the decorator. - # Since Python 3.8, the lineno points to the class/def, so need to include the decorators. - if isinstance(x, (ast.ClassDef, ast.FunctionDef, ast.AsyncFunctionDef)): + 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: Optional[List[ast.stmt]] = getattr(x, name, None) + 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) @@ -174,8 +180,8 @@ def getstatementrange_ast( lineno: int, source: Source, assertion: bool = False, - astnode: Optional[ast.AST] = None, -) -> Tuple[ast.AST, int, int]: + astnode: ast.AST | None = None, +) -> tuple[ast.AST, int, int]: if astnode is None: content = str(source) # See #4260: @@ -197,7 +203,9 @@ def getstatementrange_ast( # 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 = source.lines[start][0].isspace() + 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)): diff --git a/venv/lib/python3.10/site-packages/_pytest/_io/__init__.py b/venv/lib/python3.10/site-packages/_pytest/_io/__init__.py index db001e9..b0155b1 100644 --- a/venv/lib/python3.10/site-packages/_pytest/_io/__init__.py +++ b/venv/lib/python3.10/site-packages/_pytest/_io/__init__.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from .terminalwriter import get_terminal_width from .terminalwriter import TerminalWriter diff --git a/venv/lib/python3.10/site-packages/_pytest/_io/__pycache__/__init__.cpython-310.pyc b/venv/lib/python3.10/site-packages/_pytest/_io/__pycache__/__init__.cpython-310.pyc index fd5dccbeba7e43b98279b6e07c929f07f8ad6c3f..6e8715a2758ac18f34b23d070502ed37571cf039 100644 GIT binary patch delta 195 zcmW-ZJqp4=5QTRq8}s8vwDtsP#A^h^MyoVKV52N-64-3;965%)m4ye{)*kU2YevwvMgnE?@gg!W41l;eot00#SLqC$vIP?0s~D@1~Us{Q3~QvEv`_8Dmt+| z{!S}7_sgnnJ6Ac~wf%VB6(Pk|kR0=cPZ2MJ8*Ghefik~xwQY1;nVZ#wbJ0#OK0>UF XLA*1YOcTlYG;2vDnFPr}0FQrv{}3%; delta 144 zcmaFPw1tT`pO=@50SNZ}^T?bvkyl!Wp;!gTO<_o3%wfo7jACR2v6+BurYI&Ln>mFg zohgbrg*BK#lWk&&o&_^dbrBPg@Y7_u#aEJAl$)8Cm{VSq31ouBi$E%FvB$?J=H$f3 cuVg4<1Lv=8l52kBFh2LnN04D^9$ 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 bMND`dW@#4Kx?QlHs%76aN`~Q_R!05FBzH zy4!Sh-9_DNdZ>G{A}Z2vJHi*$J5IAIYO*%%^~9X0qjyd;q%Y=WU61v|0kMGI#`i3- zD3C`P+8$!1PH+JRuHZ%z`-7T$GDqxy|*xdFjdela@Fvm-M@QaYQ_M$8A0# zLVbVI?}@L8r_p~9-*`qG#W#*hYwHlcv0Qz&8dRBhNi2)!e#gWytl&6i_B_rP#&16{ zetTWKC{Es~G!F}Q*$Q92PdILbthCp%0WQuvNvpt>U-%HE|6#94x>=l-cK6y==_y(C zRn`K^(}U_&oNwdI&!ZT+g*CEnvyZIHmSYvH;B7uQz%2UvIrZ3sW2RL&G;YiNEi5*& zQ1ah9p7jZy+qOb`W5`b)8}ege=#`F=JykBgF9+AUu@V=uLMqko6=m)6ATMNReJ3f( z#&_dfuJ5#@o^$CGgPtt?D9YlFjG~Y!53~E+QsgZUK3?7IcI4`fIPaY9=Tfb1ch!x& z7q{f<`L*@6)tfTAxtb=|R(peDvzxuP^2(XjJSpU947m|sm-%XhJ2Ed;qoli<$8AYd zT6uU9^}E(BD|5|tn{6Ws?m8o0xFZJqy`n~gm>JoSIep)2i^?()>blYQp3gV2stCjYJ%=J68H^6JaMgjh(rq3t7DEf-tLvL-x zX}Y4HI&YAyBEq9MZzYK$EH+wXfyqI2G6r4qQz#1Swsn`?LpZ_mtk2Zkh-L7BkrnX~ zh$^y2RChFi+Hn;&)gPm!p6$gd?gV2$J3H;Cdl%Y4)-8gKZYEC!+ew-Rh3aRmxR8%w zL3jm4*Pxa6WGiVWEsfBrpr6w!$39@7p?A}fgV44&!9x_6ghMA00uhWiZ6?BeMD7$S z4hmchK;}t3w|(=XX(pXsHWA? zNtP5*R8|R-f^4L1qMifNp^C;R2e03b7$u_xKc6sI%9Okx1>>gTR*Z>Uc6fSbsQ~Ma! z8=L{dLO~4Yz?K+IR50dN`7Oi;LZ#WV|TqM@@8A!d&;GI@PA3!MN)P*%Xm895pwjMn+b zx#eh#a2;cWYy^y6Ej*xUZMm9Ak%3f*kA;>01r&E5C@;aR5tyTIUl=gIVoF?!yxClmQAIJ-V`^MZb zWdNLY^guI`T-?hZkBcRLvYs}EF%gEI5sschMNuM@;!f$I&(h$zpFq z!w1I^KYScNAozXp!*7A+5kI(^_=$^s*3W`|Hn#m~t#`hP1FTVuAQmP|Iu*3PLVS&5 z37TUz#>_kaU3}@M{<|e}Xa(pDvg6qL8{I;lg~?l~jNwO+CTU+wx9x5}6*LF9QJi8I z7hJtMS<%(21v$-jJTSA}1fh(D)Eh>eT+l!j)%MK#(5Ok9T4$G@b5rO9lfsjY5etPXBI=OCxn_NalfZ42kjLHt5TPI+cV zZFA{BZ@bdddb*_f$NxeL}mI9poodo;TqO}Qb zy{I|)qNZdue0`>Akc#yqffzp{^WdbC*Rh9AWv+R6I}ycZxT0RgY)cPBB;!u$C7Hkm zu&jasy4#dA%Sx*Y-iLhDsn*Eo$;N_~OS@b;DVXve3es73p%O@EZP~)Q$9F5x1*9ST zkw0>8A_S6-ad4 zeM657`Nn+&8^i-7JrHc%n2&EIVP9!=zujB5oaOSinJ6Q9v>-3m$q*+h=XCtmmW>nWw zL!HJO)e04>ROkuPZdJWT#ThCHaYf{&Xdde8RO}@=JyktRQUkxlnbXoqLvT7CdN%(% zclejw<$tSr`~k1sfr0>h9im)$Fi+%cYf; zi_usJO#R>w=+@ea`#ydVfOQ_K59p=r3MhdVsfhSD!XxS%xR#Z1QYtp?B8Lul?YIE77I(i3m@|Vsy4aadF o!6zSiIO)oH?ctGyr6>4t?WtdK&wF6{`9lt`@W?B)N6dfm4{rM!d;kCd diff --git a/venv/lib/python3.10/site-packages/_pytest/_io/__pycache__/terminalwriter.cpython-310.pyc b/venv/lib/python3.10/site-packages/_pytest/_io/__pycache__/terminalwriter.cpython-310.pyc index fba6bf2a6be46d064bd490ae316f5f7aa3d06815..69653ebd7c036f9b9082f7c4747634aff24fe5fc 100644 GIT binary patch literal 7847 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 R0GuCu!4;iik(Fo6{{~43vOWL+ delta 3625 zcmZ`6X>VJ{ad+Rv+oU2(wtR?EW5}#(#dhkr@$JO&ZOO6Zi?+ki+!giY$3y!bC5t9O z#}d#Zb=%+-MNkB&gcb;bB1M6I4A2j0KlC58c<6_Ib-on+R;01jnWZROZc1V2&Cblu z?9R2TPmcce^+Y8ej}Z9%@!47XZ~Y%8`dY;O3Cuak5>7dDXqKuk%QE%VvKo9fM=u#! zgQAQMxS2H-ZUAm&Ex=7~IiXTG8&>p?6DdWrQNY6@HpO^^N0;<$oX3O(l*E+I<2(VB zO`srT{g5gOsIE$QI? zh=|NY`CbuTliRmW??~?FuPezI|E`Gh10n$$;!`GngCE3*hBM&|zrw!|i>*ACA z3_lCzUg1~ydC{%RTv+?!0Zo39UjoU^d<@nPi&PxWJkBq}k{+Ic1iR+{)|jI)f6%-g zH^6Iw;n>A#?+NoiFn12Bm|!LpFkw1B3^@{2^8Ca6koNh~8Z^^Dl70R!t&?=X|GRbO zTWa+fQeOxwOoLD^XP0d+mpkd74juQK;VqQ-zYYxyUqxX|woj?N{tXMmQ#Hp?>3zlj zXXwmP;$jsyXoKXSbSH?s2~)@=)RSRt6}SIakGuu68@?MJ1iQZoztd-;%p`*BOVU1# z^wHb?_Q=7sS<1_Mwdw{}pv!a^m~?(K@^jGF6@3-z=wNhU+jCuAzz!lu9NYB@l~VQC zlXOZ+$a%rxp-t%uH4jfP| z(5bo8fmU*hvIfHI!Nb;t<}r`ar28>|29@XiFA^tO^-zP%Xv=zou24#@lX9<4JY(S` zg$Pg(TQ<4ALYFOWtk5Q8-*efUq)g{YgUO4~<3cMSjd|G_8FMV45q0h-tMIEQYGoUO zCiZ~y@>LNSdADHO=}Z{9wp(>0bo}R!C!)PF~*WKMw3>OvVZH57}dYjH~YWZ)H5J4Qv>R* zdk?0n@nbfj_xyh)GyCQNf!ag$8Lgcn4*_qd$i^V4zW}c>d4)KcxnuNW;ce7Owv+H2`;c19dXl;{jZx0O=yVr@?N2ivwOLrLxmcD_^fN3rViL;4uNpfu3Z=D-4B|6ynFD~2hEJ~sYIWRWkRw7Us=Rs zm{S#5%6roGuXPV}%>oH3Lsi22DD%kwVRz=pMsEi3oXU!vTdB!{_&vzEeE_;G2DfF5 zvQ>RmHxh7nCTJJ+zw91t$?pSyLpZSz!Z5I@y8!_8>>(xEWRUS>hRGP_xtnM}|EX{3 zJcgQ@dg$nmS+bhC!VP=?HQSFQ(}^@qsGlg*Pjq2lr+!){xd~-4PwJb&a}@kZ8k!ed zj&qBLR+z%VE0jkbvqi0GHLU9(t&@g1lbG4GkX=*5cyu}0kzIiZKcZ_gsRd_UCblMH ztw1zUW}ajQ;`Kvk;hs}>UD}%#>7qR+%IUTP(@>HA=UaEQut`7@iClH_#dbG@S!zt3Y`|0x^1d!JDp>FN*tW8lLVb9}7~KZ%&R*Oy(xfj$RtgT^c_%8t5Euk|6dX zBf|oAw-^|%x8R66S1h0P_P@o``EuGW&*dGPrvcW)q4dy$=FmubyzDF>1?bgIbE`Uw+LHune@490Z?{uYB=5i4wqV0FFx~rg&RHa~Yx3}^b zF|?@c$d3qV!IJgph?lQRxdgI*hT)zC5Mwc>QwAl}`3pfYF^vHY=@Dq0`ZN8h8G@Fn z{*bRfOPVjv*lK-iCugq9z0khpNN#-#YrSp^-uS8|z+H}|h*PZcOs_w%eYo`=pc0=4 z5?ijk0RVg`D^(%Ag+Q~*p1K|b(-j`*mv>M=b3Ga8cPkaA-Ge<`itV|5Z({$H_?D9& zAi$Hqa8;-lqx$S1Ar{#UT=xh57u&D5w)Ca{2FffgiG-c75-D^;OKTDbl<2=L7)KS3~n0Ihw~S%T1!QiazXaSTgaHA$V|^%v?y On8j$q?252NH~lZu3M_8` diff --git a/venv/lib/python3.10/site-packages/_pytest/_io/__pycache__/wcwidth.cpython-310.pyc b/venv/lib/python3.10/site-packages/_pytest/_io/__pycache__/wcwidth.cpython-310.pyc index 9c85d50f12edaa0dae9a0744183c814fd3689fb1..34b1f123f297949c5aba64040a7137b42c5609be 100644 GIT binary patch delta 496 zcmZ8d!AiqG5Z%dc+9oEof@Z!Z=5JJeNG>~Lrl2G*0gZ+bigm}|$ z@Gti2LHY^aoZWgT&a!W2-pAMU#c+l8QIs~UGBN$O|or*l557GCSa0DC|C&* zfQg;LDS5#{aR9qhE(&qLEXcwX|d8gubLE0_n&d{X5WywkN0mFk$P%VctHM*;kbKs zGU#_9KZi+o$&UfLN3NG49Zxy#n`*C8SSCgOoZ-aK0*?&sE^^t-(GGMLDUONUz@a%F zEYjLmoKaQ}34}X>0j(bRAOKH@qFJUNdj|Rj29|xYSNx~@+K=N|OE1mh7#*tA-mIf2sq5fCUc8;sFd#e*tZHVE6z4 delta 427 zcmYjM!AiqG5Z%dc(j;1`Vrwpfcn|`DKOiF5gC{TEa*?vTrGYfT*@S|Z9@-y}eud!0 zyLb_QB3BXVCwLKOQgS$B7CkJ~?$w(mlGuv+cf_!ny z>#032=D1Bz2YG?xL6+WWw6n3m$kOg4Rpkg9C1)bBOXMo_Y}ubk({vnDX0|8@w*x*s z4?+-t`$Wa9Ux{Ir%d#jkgN_~Ub5ml?)4XgXo8C2nMrx&UIxbX|=`(wcv0M9W6Fj~P Gy`vw;jaAtI diff --git a/venv/lib/python3.10/site-packages/_pytest/_io/pprint.py b/venv/lib/python3.10/site-packages/_pytest/_io/pprint.py new file mode 100644 index 0000000..28f0690 --- /dev/null +++ b/venv/lib/python3.10/site-packages/_pytest/_io/pprint.py @@ -0,0 +1,673 @@ +# mypy: allow-untyped-defs +# This module was imported from the cpython standard library +# (https://github.com/python/cpython/) at commit +# c5140945c723ae6c4b7ee81ff720ac8ea4b52cfd (python3.12). +# +# +# Original Author: Fred L. Drake, Jr. +# fdrake@acm.org +# +# This is a simple little module I wrote to make life easier. I didn't +# see anything quite like it in the library, though I may have overlooked +# something. I wrote this when I was trying to read some heavily nested +# tuples with fairly non-descriptive content. This is modeled very much +# after Lisp/Scheme - style pretty-printing of lists. If you find it +# useful, thank small children who sleep at night. +from __future__ import annotations + +import collections as _collections +from collections.abc import Callable +from collections.abc import Iterator +import dataclasses as _dataclasses +from io import StringIO as _StringIO +import re +import types as _types +from typing import Any +from typing import IO + + +class _safe_key: + """Helper function for key functions when sorting unorderable objects. + + The wrapped-object will fallback to a Py2.x style comparison for + unorderable types (sorting first comparing the type name and then by + the obj ids). Does not work recursively, so dict.items() must have + _safe_key applied to both the key and the value. + + """ + + __slots__ = ["obj"] + + def __init__(self, obj): + self.obj = obj + + def __lt__(self, other): + try: + return self.obj < other.obj + except TypeError: + return (str(type(self.obj)), id(self.obj)) < ( + str(type(other.obj)), + id(other.obj), + ) + + +def _safe_tuple(t): + """Helper function for comparing 2-tuples""" + return _safe_key(t[0]), _safe_key(t[1]) + + +class PrettyPrinter: + def __init__( + self, + indent: int = 4, + width: int = 80, + depth: int | None = None, + ) -> 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) + and not isinstance(object, type) + and object.__dataclass_params__.repr # type:ignore[attr-defined] + and + # Check dataclass has generated repr method. + hasattr(object.__repr__, "__wrapped__") + and "__create_fn__" in object.__repr__.__wrapped__.__qualname__ + ): + context.add(objid) + 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(f"maxlen={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/python3.10/site-packages/_pytest/_io/saferepr.py b/venv/lib/python3.10/site-packages/_pytest/_io/saferepr.py index c701872..cee70e3 100644 --- a/venv/lib/python3.10/site-packages/_pytest/_io/saferepr.py +++ b/venv/lib/python3.10/site-packages/_pytest/_io/saferepr.py @@ -1,9 +1,7 @@ +from __future__ import annotations + import pprint import reprlib -from typing import Any -from typing import Dict -from typing import IO -from typing import Optional def _try_repr_or_str(obj: object) -> str: @@ -20,10 +18,10 @@ def _format_repr_exception(exc: BaseException, obj: object) -> str: exc_info = _try_repr_or_str(exc) except (KeyboardInterrupt, SystemExit): raise - except BaseException as exc: - exc_info = f"unpresentable exception ({_try_repr_or_str(exc)})" - return "<[{} raised in repr()] {} object at 0x{:x}>".format( - exc_info, type(obj).__name__, id(obj) + 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}>" ) @@ -41,7 +39,7 @@ class SafeRepr(reprlib.Repr): information on exceptions raised during the call. """ - def __init__(self, maxsize: Optional[int], use_ascii: bool = False) -> None: + 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 @@ -62,7 +60,6 @@ class SafeRepr(reprlib.Repr): s = ascii(x) else: s = super().repr(x) - except (KeyboardInterrupt, SystemExit): raise except BaseException as exc: @@ -100,7 +97,7 @@ DEFAULT_REPR_MAX_SIZE = 240 def saferepr( - obj: object, maxsize: Optional[int] = DEFAULT_REPR_MAX_SIZE, use_ascii: bool = False + 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. @@ -111,7 +108,6 @@ def saferepr( This function is a wrapper around the Repr/reprlib functionality of the stdlib. """ - return SafeRepr(maxsize, use_ascii).repr(obj) @@ -132,49 +128,3 @@ def saferepr_unlimited(obj: object, use_ascii: bool = True) -> str: return repr(obj) except Exception as exc: return _format_repr_exception(exc, obj) - - -class AlwaysDispatchingPrettyPrinter(pprint.PrettyPrinter): - """PrettyPrinter that always dispatches (regardless of width).""" - - def _format( - self, - object: object, - stream: IO[str], - indent: int, - allowance: int, - context: Dict[int, Any], - level: int, - ) -> None: - # Type ignored because _dispatch is private. - p = self._dispatch.get(type(object).__repr__, None) # type: ignore[attr-defined] - - objid = id(object) - if objid in context or p is None: - # Type ignored because _format is private. - super()._format( # type: ignore[misc] - object, - stream, - indent, - allowance, - context, - level, - ) - return - - context[objid] = 1 - p(self, object, stream, indent, allowance, context, level + 1) - del context[objid] - - -def _pformat_dispatch( - object: object, - indent: int = 1, - width: int = 80, - depth: Optional[int] = None, - *, - compact: bool = False, -) -> str: - return AlwaysDispatchingPrettyPrinter( - indent=indent, width=width, depth=depth, compact=compact - ).pformat(object) diff --git a/venv/lib/python3.10/site-packages/_pytest/_io/terminalwriter.py b/venv/lib/python3.10/site-packages/_pytest/_io/terminalwriter.py index 379035d..9191b4e 100644 --- a/venv/lib/python3.10/site-packages/_pytest/_io/terminalwriter.py +++ b/venv/lib/python3.10/site-packages/_pytest/_io/terminalwriter.py @@ -1,13 +1,23 @@ """Helper functions for writing to terminals and files.""" + +from __future__ import annotations + +from collections.abc import Sequence import os import shutil import sys -from typing import Optional -from typing import Sequence +from typing import final +from typing import Literal from typing import TextIO +import pygments +from pygments.formatters.terminal import TerminalFormatter +from pygments.lexer import Lexer +from pygments.lexers.diff import DiffLexer +from pygments.lexers.python import PythonLexer + +from ..compat import assert_never from .wcwidth import wcswidth -from _pytest.compat import final # This code was initially copied from py 1.8.1, file _io/terminalwriter.py. @@ -28,9 +38,9 @@ def should_do_markup(file: TextIO) -> bool: return True if os.environ.get("PY_COLORS") == "0": return False - if "NO_COLOR" in os.environ: + if os.environ.get("NO_COLOR"): return False - if "FORCE_COLOR" in os.environ: + if os.environ.get("FORCE_COLOR"): return True return ( hasattr(file, "isatty") and file.isatty() and os.environ.get("TERM") != "dumb" @@ -62,7 +72,7 @@ class TerminalWriter: invert=7, ) - def __init__(self, file: Optional[TextIO] = None) -> None: + 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": @@ -76,7 +86,7 @@ class TerminalWriter: self._file = file self.hasmarkup = should_do_markup(file) self._current_line = "" - self._terminal_width: Optional[int] = None + self._terminal_width: int | None = None self.code_highlight = True @property @@ -101,14 +111,14 @@ class TerminalWriter: if self.hasmarkup: esc = [self._esctable[name] for name, on in markup.items() if on] if esc: - text = "".join("\x1b[%sm" % cod for cod in esc) + text + "\x1b[0m" + text = "".join(f"\x1b[{cod}m" for cod in esc) + text + "\x1b[0m" return text def sep( self, sepchar: str, - title: Optional[str] = None, - fullwidth: Optional[int] = None, + title: str | None = None, + fullwidth: int | None = None, **markup: bool, ) -> None: if fullwidth is None: @@ -151,20 +161,23 @@ class TerminalWriter: 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) + self.write_raw(msg, flush=flush) - if flush: - self.flush() + def write_raw(self, msg: str, *, flush: bool = False) -> None: + 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) @@ -182,52 +195,64 @@ class TerminalWriter: """ if indents and len(indents) != len(lines): raise ValueError( - "indents size ({}) should have same size as lines ({})".format( - len(indents), len(lines) - ) + 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): + # Would be better to strict=True but that fails some CI jobs. + for indent, new_line in zip(indents, new_lines, strict=False): self.line(indent + new_line) - def _highlight(self, source: str) -> str: - """Highlight the given source code if we have markup support.""" + def _get_pygments_lexer(self, lexer: Literal["python", "diff"]) -> Lexer: + if lexer == "python": + return PythonLexer() + elif lexer == "diff": + return DiffLexer() + else: + assert_never(lexer) + + def _get_pygments_formatter(self) -> TerminalFormatter: from _pytest.config.exceptions import UsageError - if not self.hasmarkup or not self.code_highlight: - return source + theme = os.getenv("PYTEST_THEME") + theme_mode = os.getenv("PYTEST_THEME_MODE", "dark") + try: - from pygments.formatters.terminal import TerminalFormatter - from pygments.lexers.python import PythonLexer - from pygments import highlight - import pygments.util - except ImportError: + 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 - else: - try: - highlighted: str = highlight( - source, - PythonLexer(), - TerminalFormatter( - bg=os.getenv("PYTEST_THEME_MODE", "dark"), - style=os.getenv("PYTEST_THEME"), - ), - ) - return highlighted - except pygments.util.ClassNotFound: - raise UsageError( - "PYTEST_THEME environment variable had an invalid value: '{}'. " - "Only valid pygment styles are allowed.".format( - os.getenv("PYTEST_THEME") - ) - ) - except pygments.util.OptionError: - raise UsageError( - "PYTEST_THEME_MODE environment variable had an invalid value: '{}'. " - "The only allowed values are 'dark' and 'light'.".format( - os.getenv("PYTEST_THEME_MODE") - ) - ) + + pygments_lexer = self._get_pygments_lexer(lexer) + pygments_formatter = self._get_pygments_formatter() + + highlighted: str = pygments.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/python3.10/site-packages/_pytest/_io/wcwidth.py b/venv/lib/python3.10/site-packages/_pytest/_io/wcwidth.py index e5c7bf4..23886ff 100644 --- a/venv/lib/python3.10/site-packages/_pytest/_io/wcwidth.py +++ b/venv/lib/python3.10/site-packages/_pytest/_io/wcwidth.py @@ -1,5 +1,7 @@ -import unicodedata +from __future__ import annotations + from functools import lru_cache +import unicodedata @lru_cache(100) diff --git a/venv/lib/python3.10/site-packages/_pytest/_py/__pycache__/__init__.cpython-310.pyc b/venv/lib/python3.10/site-packages/_pytest/_py/__pycache__/__init__.cpython-310.pyc index 1e9a2c84c7c70df77891f3f927a8f8ed38aa9e4c..b5edf7006b27854b2305e6f1af63e464f1669878 100644 GIT binary patch delta 19 ZcmdnWxRsGRpO=@50SF!odQRkC1pq5i1dadz delta 19 ZcmdnWxRsGRpO=@50SNZ}^O(rJ3IHz<1yuk5 diff --git a/venv/lib/python3.10/site-packages/_pytest/_py/__pycache__/error.cpython-310.pyc b/venv/lib/python3.10/site-packages/_pytest/_py/__pycache__/error.cpython-310.pyc index 5a963e9c1bc6acf676b96e09e5179a1f87e60e24..89e02db89c5b9e33ebb556a9c1415f9d5037b767 100644 GIT binary patch delta 849 zcmYjP&rcIU6rMLbJNu*CrG*wK8XBX~G#X+Mq$C0wK_LwoHO6F#X`r(h%Cg1jVpGzJ zY551p+yoLmdho=>3n$}0;IUVa9yBIiNQ}-@W8BU6eQ&;*_jcaQ9%a5|+)6xdBl&zz zF070AzI%OllXwIW14aVf)4NJN+Ev3dr0RjWX?a$64VAI&*?@eDkIFeCh;2HaBgYOi z+u+50n^{ubyNKB=wue~E?`dmZ!q>~mzvz|PCy|m!`xA=(pr1L1mNy_%9dC zo?y5kYIKJjpd&1P(D6*ic@%Dhm0CE*d006$x9TH4=N0}|%o$CX6(5Y2YseFdN!&vS zs_(@^^UCfrpr#IJXF9?So#8gFW?LxI%7kefnCExw270YWw4s-c40;1i`WS^-f+Dj% z0Z6?Ky#+PNWrLAz#OEW6=@D83Mj2toA#Q6;8d6n7tx**kv;k%N05$Ld?mfb7Xvs@z z>M!(~ER-|Fce60uN&Mf z{#SOUTU*gBv1QLfK}rDGS1YunK__lrfYk~%%5N)Y&^XuT3vM^;u#TvI9C-X~RbM=hdJeQP~@%87Dx qT6Sh4j2ZKYPlq9VeAgve~QX`0l@me8=V zlJrdw%e>hZ^auE$eOB=ZSEIw?%{(`fVfV35`>h!2?mJzVVxOi_;)HRm1@0mS;|WFsA%xDjgz62Rhd(>b`xgQ2fO8Sd?OFBNC?zk zsGI!6=5-)V6G&qsqOmDxo7To!BsNP%(~4Nh6vhgbQw=93FRjRqtt@#3P5UmX_>4q+ z;~WruVzUKxqHAJH=VLpDlCwi%x@D{9!eG@5gz zgHv_v#oIW3Rd(!$#ac0nq&$vc^~^bZAdf<(kaAr1YdxhYqv?YB?2NK*<#*@M1nx#^ z$B-DKkb(qI@WDm4y4;5m2K2Yf`Y3-wzo|ucVYu6D`hUms-L{%8s<-YO%&X^#K}#ON zNM2>UN3fuPrH4+TUq;j@k_%G%IVKm>8!vr&ME0WFaSMf|^=Ms&g@V*&-Hqg=Rx>(p uMH_3&l?T#5NAW~0HK$YhPx7j!dG$1r?*UFZaGYEpM%Aapm^o>IZ~OsSSEauI diff --git a/venv/lib/python3.10/site-packages/_pytest/_py/__pycache__/path.cpython-310.pyc b/venv/lib/python3.10/site-packages/_pytest/_py/__pycache__/path.cpython-310.pyc index 414ae522139eef49ebaa2b400acce440a8ccd9aa..de8b07abb7028d271b0c6cce1d2c355669fafcf2 100644 GIT binary patch delta 5668 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 CD1=x5 delta 5581 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 int: - ... + def size(self) -> int: ... @property - def mtime(self) -> float: - ... + def mtime(self) -> float: ... def __getattr__(self, name: str) -> Any: return getattr(self._osstatresult, "st_" + name) @@ -225,7 +222,7 @@ class Stat: raise NotImplementedError("XXX win32") import pwd - entry = error.checked_call(pwd.getpwuid, self.uid) # type:ignore[attr-defined] + entry = error.checked_call(pwd.getpwuid, self.uid) # type:ignore[attr-defined,unused-ignore] return entry[0] @property @@ -235,7 +232,7 @@ class Stat: raise NotImplementedError("XXX win32") import grp - entry = error.checked_call(grp.getgrgid, self.gid) # type:ignore[attr-defined] + entry = error.checked_call(grp.getgrgid, self.gid) # type:ignore[attr-defined,unused-ignore] return entry[0] def isdir(self): @@ -253,7 +250,7 @@ def getuserid(user): import pwd if not isinstance(user, int): - user = pwd.getpwnam(user)[2] # type:ignore[attr-defined] + user = pwd.getpwnam(user)[2] # type:ignore[attr-defined,unused-ignore] return user @@ -261,7 +258,7 @@ def getgroupid(group): import grp if not isinstance(group, int): - group = grp.getgrnam(group)[2] # type:ignore[attr-defined] + group = grp.getgrnam(group)[2] # type:ignore[attr-defined,unused-ignore] return group @@ -318,7 +315,7 @@ class LocalPath: 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] + 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.""" @@ -435,7 +432,7 @@ class LocalPath: """Return a string which is the relative part of the path to the given 'relpath'. """ - if not isinstance(relpath, (str, LocalPath)): + 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: @@ -452,7 +449,7 @@ class LocalPath: def ensure_dir(self, *args): """Ensure the path joined with args is a directory.""" - return self.ensure(*args, **{"dir": True}) + return self.ensure(*args, dir=True) def bestrelpath(self, dest): """Return a string which is a relative path from self @@ -655,12 +652,12 @@ class LocalPath: if not kw: obj.strpath = self.strpath return obj - drive, dirname, basename, purebasename, ext = self._getbyspec( + 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("invalid specification %r" % kw) + raise ValueError(f"invalid specification {kw!r}") else: pb = kw.setdefault("purebasename", purebasename) try: @@ -677,7 +674,7 @@ class LocalPath: else: kw.setdefault("dirname", dirname) kw.setdefault("sep", self.sep) - obj.strpath = normpath("%(dirname)s%(sep)s%(basename)s" % kw) + obj.strpath = normpath("{dirname}{sep}{basename}".format(**kw)) return obj def _getbyspec(self, spec: str) -> list[str]: @@ -706,7 +703,7 @@ class LocalPath: elif name == "ext": res.append(ext) else: - raise ValueError("invalid part specification %r" % name) + raise ValueError(f"invalid part specification {name!r}") return res def dirpath(self, *args, **kwargs): @@ -757,7 +754,12 @@ class LocalPath: if ensure: self.dirpath().ensure(dir=1) if encoding: - return error.checked_call(io.open, self.strpath, mode, encoding=encoding) + return error.checked_call( + io.open, + self.strpath, + mode, + encoding=encoding, + ) return error.checked_call(open, self.strpath, mode) def _fastjoin(self, name): @@ -775,11 +777,11 @@ class LocalPath: 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 + 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:: @@ -832,7 +834,7 @@ class LocalPath: def copy(self, target, mode=False, stat=False): """Copy path to target. - If mode is True, will copy copy permission from 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. """ @@ -957,12 +959,10 @@ class LocalPath: return p @overload - def stat(self, raising: Literal[True] = ...) -> Stat: - ... + def stat(self, raising: Literal[True] = ...) -> Stat: ... @overload - def stat(self, raising: Literal[False]) -> Stat | None: - ... + def stat(self, raising: Literal[False]) -> Stat | None: ... def stat(self, raising: bool = True) -> Stat | None: """Return an os.stat() tuple.""" @@ -1024,7 +1024,7 @@ class LocalPath: return self.stat().atime def __repr__(self): - return "local(%r)" % self.strpath + return f"local({self.strpath!r})" def __str__(self): """Return string representation of the Path.""" @@ -1045,7 +1045,7 @@ class LocalPath: 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 can not be determined. + Return None if a pkgpath cannot be determined. """ pkgpath = None for parent in self.parts(reverse=True): @@ -1096,9 +1096,7 @@ class LocalPath: 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 {str(self)}" - ) + 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 @@ -1163,7 +1161,8 @@ class LocalPath: where the 'self' path points to executable. The process is directly invoked and not through a system shell. """ - from subprocess import Popen, PIPE + from subprocess import PIPE + from subprocess import Popen popen_opts.pop("stdout", None) popen_opts.pop("stderr", None) @@ -1263,13 +1262,14 @@ class LocalPath: @classmethod def mkdtemp(cls, rootdir=None): """Return a Path object pointing to a fresh new temporary directory - (which we created ourself). + (which we created ourselves). """ import tempfile if rootdir is None: rootdir = cls.get_temproot() - return cls(error.checked_call(tempfile.mkdtemp, dir=str(rootdir))) + path = error.checked_call(tempfile.mkdtemp, dir=str(rootdir)) + return cls(path) @classmethod def make_numbered_dir( diff --git a/venv/lib/python3.10/site-packages/_pytest/_version.py b/venv/lib/python3.10/site-packages/_pytest/_version.py index d530ef4..e5c1257 100644 --- a/venv/lib/python3.10/site-packages/_pytest/_version.py +++ b/venv/lib/python3.10/site-packages/_pytest/_version.py @@ -1,16 +1,34 @@ -# file generated by setuptools_scm +# file generated by setuptools-scm # don't change, don't track in version control + +__all__ = [ + "__version__", + "__version_tuple__", + "version", + "version_tuple", + "__commit_id__", + "commit_id", +] + TYPE_CHECKING = False if TYPE_CHECKING: - from typing import Tuple, Union + from typing import Tuple + from typing import Union + VERSION_TUPLE = Tuple[Union[int, str], ...] + COMMIT_ID = Union[str, None] else: VERSION_TUPLE = object + COMMIT_ID = object version: str __version__: str __version_tuple__: VERSION_TUPLE version_tuple: VERSION_TUPLE +commit_id: COMMIT_ID +__commit_id__: COMMIT_ID -__version__ = version = '7.4.3' -__version_tuple__ = version_tuple = (7, 4, 3) +__version__ = version = '9.0.2' +__version_tuple__ = version_tuple = (9, 0, 2) + +__commit_id__ = commit_id = None diff --git a/venv/lib/python3.10/site-packages/_pytest/assertion/__init__.py b/venv/lib/python3.10/site-packages/_pytest/assertion/__init__.py index a46e581..22f3ca8 100644 --- a/venv/lib/python3.10/site-packages/_pytest/assertion/__init__.py +++ b/venv/lib/python3.10/site-packages/_pytest/assertion/__init__.py @@ -1,9 +1,12 @@ +# mypy: allow-untyped-defs """Support for presenting detailed information in failing assertions.""" + +from __future__ import annotations + +from collections.abc import Generator import sys from typing import Any -from typing import Generator -from typing import List -from typing import Optional +from typing import Protocol from typing import TYPE_CHECKING from _pytest.assertion import rewrite @@ -15,6 +18,7 @@ from _pytest.config import hookimpl from _pytest.config.argparsing import Parser from _pytest.nodes import Item + if TYPE_CHECKING: from _pytest.main import Session @@ -43,6 +47,26 @@ def pytest_addoption(parser: Parser) -> None: "Make sure to delete any previously generated pyc cache files.", ) + parser.addini( + "truncation_limit_lines", + default=None, + help="Set threshold of LINES after which truncation will take effect", + ) + parser.addini( + "truncation_limit_chars", + default=None, + help=("Set threshold of CHARS after which truncation will take effect"), + ) + + 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. @@ -59,15 +83,18 @@ def register_assert_rewrite(*names: str) -> None: if not isinstance(name, str): msg = "expected module names as *args, got {0} instead" # type: ignore[unreachable] raise TypeError(msg.format(repr(names))) + rewrite_hook: RewriteHook for hook in sys.meta_path: if isinstance(hook, rewrite.AssertionRewritingHook): - importhook = hook + rewrite_hook = 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) + rewrite_hook = DummyRewriteHook() + rewrite_hook.mark_rewrite(*names) + + +class RewriteHook(Protocol): + def mark_rewrite(self, *names: str) -> None: ... class DummyRewriteHook: @@ -83,7 +110,7 @@ class AssertionState: def __init__(self, config: Config, mode) -> None: self.mode = mode self.trace = config.trace.root.get("assertion") - self.hook: Optional[rewrite.AssertionRewritingHook] = None + self.hook: rewrite.AssertionRewritingHook | None = None def install_importhook(config: Config) -> rewrite.AssertionRewritingHook: @@ -102,7 +129,7 @@ def install_importhook(config: Config) -> rewrite.AssertionRewritingHook: return hook -def pytest_collection(session: "Session") -> None: +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). @@ -112,18 +139,17 @@ def pytest_collection(session: "Session") -> None: assertstate.hook.set_session(session) -@hookimpl(tryfirst=True, hookwrapper=True) -def pytest_runtest_protocol(item: Item) -> Generator[None, None, None]: +@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) -> Optional[str]: + 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 @@ -162,13 +188,14 @@ def pytest_runtest_protocol(item: Item) -> Generator[None, None, None]: util._assertion_pass = call_assertion_pass_hook - yield - - util._reprcompare, util._assertion_pass = saved_assert_hooks - util._config = None + try: + return (yield) + finally: + util._reprcompare, util._assertion_pass = saved_assert_hooks + util._config = None -def pytest_sessionfinish(session: "Session") -> None: +def pytest_sessionfinish(session: Session) -> None: assertstate = session.config.stash.get(assertstate_key, None) if assertstate: if assertstate.hook is not None: @@ -177,5 +204,5 @@ def pytest_sessionfinish(session: "Session") -> None: def pytest_assertrepr_compare( config: Config, op: str, left: Any, right: Any -) -> Optional[List[str]]: +) -> list[str] | None: return util.assertrepr_compare(config=config, op=op, left=left, right=right) diff --git a/venv/lib/python3.10/site-packages/_pytest/assertion/__pycache__/__init__.cpython-310.pyc b/venv/lib/python3.10/site-packages/_pytest/assertion/__pycache__/__init__.cpython-310.pyc index e26488a7fe34337de983a2a28bf2350d76fb2a13..7cce1963d17a3f2208714d71ab007e382a10737a 100644 GIT binary patch delta 3649 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 delta 16888 zcmaKT33yyrmFC-OUsRGxrCln?vaIr&c#9XsYi!3!EHAQSQn6Dh=|0I-E|uh4Pu}t> z4|;kTV4%B!c^~UEbO_x|LN_zeiDS=y?kmZX zl5eE%op;_n=kE8O?cRF&!`eSRr$xsjk)VRVKfiHz?puHMe6*#al#(7rVM>y%s#LjAs2i(K*Gt?3Jf4nA+*@cEOQaJL_W^HAH%ik8dtOVUduUJtw{-6QdMVd+?Jx>w>2g=J%X={|`k3aPQ>=@k-hEUX+`m0l(Bro!s6 z%9`{V`O#dsZS3~+?GkSRzBav9;;n^sW9!rFRmDv5AE2>qD0oA91HL2qjja8&hyTh{ z^LL~-0d7ujk#MVo{lzfecS_1ON!gwz%8qpZK81C#&dUnxtW0U?o#uL$Y*wa}^ez;- zJAcqAj}rYFBY ze~*OsP8sR@SjucN4>T)mxf#k2uoY%a{vcb)v=KjB#a3T7(udd@R!LR~F4JvhJzHrW z9tpDB*;=q1Ve62x-du&$PAY~98PS@R5ii@oHiBc2-N80pRz_5|8Jv`ViG+r@UH$_H-ofj^C+4R#mX1ImN*c=xh>NI6kWISih=*?y#)WcQfOvbKB8!>mFD zd)a;L07wt90kd6_4&r?n4LMZ(K8*LfWN(h5VMo{?xHIginP$h#qwIb>?!X>r?*ipv zIffd0fTi)3ML&nwgMh>A1R!H40ZsN$&Pb0`atc01Sw^UMpJETocaCM{JI{vX`!pMt z?*e1;J;qG=F0v8%9%rNSJ;6@N_ZgOxZ_e`aU1F!@+hPUzo@8V4eU?><5;@1l<@-FF zKr=3|Gk_Nv2YiH;0BvRgmf2*^`w2DuC_9U<3VPW>Z#eg z6T8xEG}9PiS}_K*S^2UtrKTUpqJ5NAuxKl5QNYxcp1#a3RhOQ@9%GNAs3+KzOsPl4 z%j^lHJjt{{CH0i3Gx}o#Db)!vb0j-iuvl)`5-%8yjsD%)LLoa;FhMl#LMq(|G^0>H(tyZ2p=@hOq#`E;@1aFB72TbPRRU`qYoU~li7Xup56EEAJ|8f zV@2c>UQeI6%Tq6|c@jSF?(yPCZd5$t+0+!>St^;FiXODGmU&fsRfn8^)i2I$iThQc zJMf6#c=mah0O3o-Ht%p#C(wR8B?G{(7%Ha}-YWjgyQ)HpVh5hmd1N;gCscIW30YACOMt%- zo2!vEA+<@SHlqo%6hCWWt)Mh7;%{T^NUgN64luht>SReIwMs@erHgeVrHw7Q)fZG( zi-z^ErJ%H%9r;ey3;Fy7i7f+mmG!YyA?YfTqE~)(SJeuQjc#@!|8lkh`Im&5F{sd! zTID1}S5rcK!nd{C@p99eVHinUYy5VU;~3wg+!p;~2&4nCTc3S?iSJ zwX%HFw1#Oh&Hnj-c=1q?rH=_MbbBSC%&4kjE7M+EwY3?o?6EyenN&)fY){_jVwN9x zz>>>nO5>ZXNIp7UL-ZN7w1p{y3gKlhN{OKquWiierM;AnJhk~cnMOSIOuIs3s1ICh z8Oaq{W-LC=CJSbA7+ck3$&qosCAqXD-UzM6pryk5mUzc`ZZubPg1J(zShBL%o_sr6 z?*wvV6XV<}vrHZrcDOCWcYrD7T!cA`D&S4TN%P6q z5TFKg3n1m4x4zuLMXKW)DawZNbHuo04)hRq0N@|+l*Dx6VO0@o<6~-{m}x8Mu z*%9`LM_MwvQtA^wYH8IHin!jgL0v1>wyrkh_8e+m<$Vq%@#W$Jt-Y({R+8U~`t=NW z;2kKXD3Gw~)il*Be%ZQ1=tpYBfi@qydaUhS*9WLk>d4|I`60bBENf-E#J{wyX}PNL zb}+t5iV*uo;$h%i72h#hD+@NAj&z!v-FQk{04yaBPwKS7!%TypqmAihO;sTD>C14x zY|ZlIA;a=MTZQs7uBcNgWR+)Nwl>;uoc1#Ge{gTEU>-5E%;YH*D`q89{rmwCo$y(c zlT3zW9~pOQ=j7CC>}Rl-__R3jOxG!obz#DUtQrJE!5qL!D@|k}-Dt0-^m$W0jfBNx zQ%_^V&C>WJA2xwd!7FK`X+(k~kTgw;sm9HV%b~0q#rNCmD_6CFfm9DqQgK}bx(Pf< zAVGk}iOW7V61J7V-30anIKe|$^?M<+P0o){>K+1P1mrBlh{-c_!7@wyMZ$vzq~JP= zUP^o?{LBkoBoZ&{XTsEgFqwFE(vgPK^c8EuW4^w+M^~O;bH<>?-V`-aTJ>@%*Klrw$UjQS{&Q!Ua`F=p^l0Bdscd-Xk9Lz?&)Zjjgk{C zft>XWiWm}K>1n)|uwjhV+#lieP<4Eez)=E}7j>%V7Z1C?zmOdpV%cp)(Y3Upla|qf znWp7W+lI<3-6y7&u7s3(d1*N?Y-u>ZF#DE7dZmz{sp^A$^-NzfvT8xfpY(M#@ecHWzYK73^#bDe`<85d0m+URO57@# z{4>PUGM^{4B{@E6CC5jSkl5zM74vDLJ+-NloSy>j;<%N}CWptFnH(R=oAA!+-L>~s zlr0Sf{@)3^M&NS<{*=H60UW(x7OPu{(mqRR!4W<_mT~3Jf29PYFrH;jV9)ts6Fy#G zyvX+v^NZp;sfH}AURSZpg&QL44-G8Af8<{z+LvSzq<#3;2@T49Dbi z+);yXXeWv(kNC~1w)^79?D#Un()ZlN`cJHAIpj@@`lWFp!KMNX zXl1R8VV^c^&DPf7+B5R*qw4Agi9E#Pkvhdw@0a(u2pfl{ZkN{-*m4sSS>fw)_sl>^8? zj~v9ufzV!bm&z2Dx~)y=;s@*6E9e3JGqvn{Kq;!{6x+uU>Mp8hf|ijdr(NaSEo0h) z{cAKUwmu7))`+;yj9&KJewWMkW8-+GBYu?s3RR}Oj(#yW;dpqqIBN30N0J)*jxBKsq#*1=-tHR8`@ZO(=l8zKt}aeF7k#_q@s z%0(h2s`Hrm+QxqMHF0y}w&n%4>OzJRS!miN zFV-^|@G@P_5&szo@Sgebf;xzuT2J+e$2PUqhM!WNQlHYE(w{O$JPc7m@u^Ll$?@9M zxzu+SHt5qT^WUL7qZT8!4yo@wt?^^F!2+NiBbr#bd22R^)R%3~(*{!CZv~!)ZeV_- zz2ElI7YpHtLT!XOWBaGnCZ$|WW11ZxGlw|(m10x1gpa#9K=sa2#b0hdsYXQamOWx( z3(kSWl`XrwFQF4}s3%-6OXqLm6EQYK5ONcI9X=2#{nst)-AIL8WwEMFoJBAR+s(Ar zid|ceG}D^CsHaYr-%wZb)A+!WS%KGp_{`RIq1UOT2?BpFUfbH@?Z(ja6{4oUK@Ewe z{e4U2JTIkGSxG!Y?$)hUgYC@W9P|ErD|dp4W_Q6KiA4GUy>Tc~fj7sLS6*y`zHDE+f4H$5w&-$|`x~T&uURt)Q-A{h&6yE%Tjh z8>p+@ z7=?jMX1IWzk;&W={j$Ihi{I^U*LD95(RfdL=M``-@cC+xKz<*!sPv?G*F9Tt_KVoF znpa>E?Et3pdti!sNVOs6Eb+Y8@=4sE4@e7eD`Lvtytk|s4T%NIu&|~z8lgkA@_se3^N^~jIanJ@%La+Q!7f*11adcLn#|)x~dtX$iTr?<3gr4EBV+n zN)ZjCw?z%{!~DF0X4bL*3qDP!E}<)i{0_6o?Dwcv_7=m?&XQ|JlNjhT8c}HXS6GzQ zT=v_D(85Oa*@!~rp~!9H39TO%;|Of23?k{HDyi}3RKC;pPuI^t@S>>!R2Rh)$I~#P zqtV~B5~Hxi@^v3m?ZETu5tJ$O`==YNCTV#FtS0pQC&Yt;?AQ~um5Alc%hdv#r?k>5 z_b896Xlsidm?2a35KPrzIRuW7)jFl28L3gF9Jbo*&=k>^f*vVHL8s=lOOBWwp6q4KZgfrKzq#dw>c6drhmVcI28hJ2+ufO`$MPH?U znJXlV{%6}?x+dqH{D9hv8z^LzgdrlfuSkf0yRWZ}lLfNZ31%`QlQ_FEv2saU@Q(n9 z{Rdj10?Y#g&_|y;utME0etMw0RT>qvXIym<>@VP$fvllz|0voAwme7*Ce=;hEOx>1 zL07ut) zd}yF6BEFy7Tf=^=aRXA~HwX;(mS>d7Pj;2O2YrI6Qb=y!S}QR^vXz5oQCj zRPH<0j|na1MvDp5BW+2))~&|G?~bfPFIEh0T=xpKUv<2v%mRezY)@oyi+bW?IUI&$ zFSLpG5B944;){crNAaV<2RGk~%5l6vtBGHrhCD(o4(!BY%MDG^=@p%L^C4pM%p0nT zICHc{PO6519wwSV^IbC5~a=yNIT)7$y~w86*Ka9WMR3E zuS)C{k00BPgNUykyP}51sL3Ui!!40MzGN-+8(|bggL7E4eDP$~lD|~--C)@B?AQwN z>Er1BwKtD{PVIk+a?^p`@M%u#;;QO)c2P^}Cnig$W<52;CoCK<-68(zf%{R;u5_)Y zo<_W(X^juSqz z_~YU)PTZ4xH~Z0;v}u>#H#-I?25)hLm{ ztHnmj45>{u-^wSD*2W}`V2Ypl%YmZTYGDB=RzC|uWopyy$P<_n&pv!aHN=k}-c#vA zRwywf*Fjg-&KM}J9*ikj7?c$n61VF|;q;7>Gaa|%(8Uds!)wRv2Bamx6Q1s|6Qqks zvqDz|q;)R8q-JkKj^%cvwSvKfNAsH8JQZ82<>GOwrrhi%Rts;Y3T2V#9g6H*+&|mX zsFXcL|E)f{DahY$`vE&?p>? z>t#6o;_q1ZMp}he^?8zv_6dAI+Q74me|E7-y+|AQ(_*EWt|s#rtLLcBNdVk)!2JaaaVCV3hQ}wdr!At9q~Px-Amz*l2>Sy9BLI%?STV~l9Gr0U zfpLp}h|)=d;pDe8DkVREpZwg3^Y=MT!XKlwiswi+hmfgBnMm+(a&g^=@*pvg=`t`m zhO1N~QVuyalz`()Tz`U0Mgu95;Y8pyT*ws0hh+pV)!~Zhk5E}O3j7n23Gr779W$KV zBS3#0gs|f;nxirk|Igz0BRMrJhDLXsl!Fl-E|^(fmFfI272&r`oS)*%fj>)wYmk8C zQXI+Inwv5l%aR1jxX#l!FA}Q4E+qF^b+_@)`-GM8V-ls5l`bUNKo>Q1hcS zDm$oA9mY+sc2VsR)1{^^w|xslB#B4T8N2b6Y!S`%7+zbS5}R^6T;$PQ=UNJSrlRiJ zqlx5C5ZH!PN6RtCx7!Wgx$8jNU*u)+?ObCW32`aTalGalSK{}%rMo^($?s&H)VQNa z^UcVh{aYGHTI+Gxkre-ot4-+h#V+cMIG1k;&b9nJTHYA4lxf{EAZtBS;w$+b6&F<@ zJm~_GG99+PGwKMb-oHQ_Qjx_y`YUP~EoT0g1pb-;*)sfZ2>cHM-z0FWWAiWke2Yl` zlfZWfd|XAJ-?5*;TmAeAb&(?ealK7;u!TC)HGm(kx;tmDtHM*5 z487nwPj-+$CY~rHE0zyll$Ni9yY?92`Fbl(IAQsQJTA;J17cT&ld9N3ikp%6MLg~? zjFSS_FG`#cqm6i);JsDz!CjP;d_x8bX*xQi$O~T0He4h$p{5+Oz0)!4L2bGPa=8^| zur}Q`qreqaA@|`sOZIlF!;VR%S_^t7Jki=wRcbz*GUh`lK$}k5bu-eXgP#__?TYW& zHRXuy;a?XsWA)9{w4mgxwIk4+vr4R9d~>XID?EIwN2+bor3w^P94d)Qbz5Xmi;%w5 zj=2jW8$WMnWWV?wI#jgD@(P%lqMEk!;n#HBL=7)G9@w(W` z(XdIix5Sr=n=2H;PxVTNfJ=Kvn%p#<($ta`v3oE{#*w3qPq+(DN^L2MWdQwoT4GWt zQ^3}3`=<$`HH`B~bHs82WJAlHB=@Rpd4=yHt{ntOoG&)_=H$7Q*qo#?ze?aw2*}Ow zDZbS=CwYxw&A4IQ^!p4wc!LfJf;tJ(q#6}po@n+{?iTeT$;;+tvUtZc zNeXYl4kWoVCwsPu6=#+~z~o^-iigiM?4o0Yl-~&)8`yc|fy{xOhd7-k@w*7PDwKj= zh$_!p05rz4*SmhHiZ7j6wn27(QO6^$=%}jTHUm8;2~L7OPLiWjipJY`bfFtEjk~$iL79i~>K3Yn4{v4$*m>SX-b6qWnS4vNB-&YPIrW4E~L?+{XLxj7xv8HiL zA|J>>8bI_70r$~2#GT}zcP1=8Z>1`a+s1SdsrXGxqhbdd<;~VJyC{{8C1$^wj=QT6 zt+=adKzeC#2N0IS$*TvdM=#?R#&jKtN{qHL4H-5axBVpL%3(W<-(q+q^x6U7z8AcZ zcYZrUbK+0=2i{Qca%X!+7tL2yt5KL>IZf9t z6z!Td2KH0}d(w-&h8>CM4IR*taf8Fvn@tG%5G|(1@lL2Qtq#XixU3*2+0});RI1~H z2?*DdEtqc-)u>J`ZE@t>Ix~S5B1AzOjUOdI0@m>jx_KRa*g8KO!3e;kzSS)!gyf64LL!VD2?i!=O$N;E160ic7ievD9wJicU~&^ z92)nI2bVR=-M-DmdXCcc#lY@PLkp$?H`_2-uH{vkVy zoIo%Z$IW>J#qtKskZ!{Bo~M<(ciIQ&ukOHZN}+G;Jjp@tj8@GXK+g5HTFGmR*n)Xa zKUEZ>Owta)m4~?s!>AOS2$}C=5YgCL+6#JFm#1Q61^KP0RfC)`NXVsxA5Xt_AnVAG zUpPaX_vN)JwevGjMm%*>dfA6?wI5HwhP6`;+CKQI7fxW;m&4@fZo+qdskxz zAzIuhc!M3zCz=&lW`XIr)#w^tFvDmhe0EGiqxd;2X3Y-6bc>FX z6%h}#*OMrfDlFuxF#d@6 zncWJ%{Vlsi`t7jg&SHyH!);h#kP+i}Z`@q(VJ-$tQ~CmAmHo z?}$#xtvcu8&PD)!GFRNfGRLXRkIlvs-X035rA@j}rpmTV$b3}j>jZ)X=E4}Mq-zAq zKpN@dd5xM=0{l%PNR#gegpuP`aU}~`i>`O06DCc|8#D@tr69QH-pa^YvGKJjd&xgyn%93MGhG*?d59IE=C4`HNA6G(?yM;EB(R7+T& zHTFo!gw-~fN>P}3WFAUWv3fEAKU>pa7Qz#UlB$zRitgr;X{FYQmP`E=P64IVTArjq z>LSoh;7I}`_uN~{^3L;B`jqFNQUiJ2JInHv#0f*qSh?Xb(>jIw@)Rs^k96c9pQ5hI zbtPrK^fu+tUcy0Q)%ljgD(!!1u|G^%{1f-Ymrxh6_5FtJhD+pWRdf&5t}*&VW}M z19U%H+d{Xf=s}d@Chk?;h}UTujIW;XCA=+0+#B}ewnmAXV}znabU`apAlH%_|)AEn^mO@fpswoN$j(HdAdxpUVLHdlX?IbdvlkM zANwk0{BHn`XU~y?dj}x_7$tq3z@lAu0%;$nwbTMc^FaGW>VE4r=mr>Fevgvja!I?+ zPl)y>dbhck39CN>@bCmM4N#{yLcSDZmUkV=$9|8}3cnI5&@Y7Hp5wVE1}bgT2)U=} zw9g4;=q_z`_!Q0$;S)SS)EOc~XOkzmIg&f?{v78lQM`0{s)(Nz@)a~yi#qfmSYD$>oGnvp5;;oUA7vARt$- z+)-rr^N50Ll2hMqn;bCHI!&p9@mHOk*Ks zqx6+!XfsUQ&9f}{5h#ASq$Aep{tk1OD7<^U_as%25ih;FtCAx0DglSU|03{Z0#a5y zOc-50cf}1kznt!O$?La{7q6MX+%c!m=aCfkv(#b9YZ@&W#k^ahOR`_k$3EY zc8_bK#;fbrah-d|OZ;u#3ONY0C;h3|M%i^5cWl`8qXa6&AV6|5>; zDO4uo9>vS_(E|P7)e-&~2xaBuntW|v(JTAP$*EjbB)4)qcR2aG-L-NHQK6Slj>Q8u(_8NGR!*m?NJ(pLL?}q%*!ygCzcC`-7i%5N7chD#PGZq#Yj;d{$@n z`JKseMSFZvtWRJ3u$(s$8jU(~ZNs>g{ER2;I zp7#i3(?^F(C#4Bl4kEW+Sg!~tugMirG)H=7L@wtfCd$4BZz8uLr;t-4e~ z)e5&~lBsY3sxS@<8_q4(KH1yfdG^_n|M|`z{Lc;!_qso1?S8uRv|B7|9L|zlIxtwQ zf4TSccVBc5j{fNHbh~>`j}E>#>`EOLfKcOa4!5FOt`$;;-9WD#-qNp44DaG^Fwv;4 zlLlQUO}dJ)PK~r!3S(5a@?%T0TY9C2a!>C#eQF@8_#0sS7lcB6euePAMn)sB2>hlF zC~7w53NqcgCV6E@_%k+*}ucgay4`lEtdcj-V*TC;)u1 z%v4Nc?mUblbUMh;$$H4MlM5EGEJcZ8BlIrJZKGINH~WBv+|+~4i^HS+n_E1WcDvoa z(VWXIQ2QASmOXWPGFW|dSuvXyB)zP&LCCp?TidMV6m}%y!g-N|aj`k8xBkh6afokD zU(}v;cb@F|pLd_MYLe*#%<6HmS^`|VZ=!uqC6SN;I)p!SY;lKKwv3I5>jYoy1VwYs z)@J9mn46fD+uY5?6&&kV_@|#@Xg1tsdh0n2>Jf)}w29FnTa*%qxVMhuQ3qM-kyUU# zO8?^7rkdYUO8@8C(7uJ{t@FLqR)5fQb&t|T#N?^DRq4WPGUOBM`TEzB&WpZ_@{G(N z$g*`}#nqgaJY)y`2HC~YmrZ_Fbcfb?n!EA?U7C?&$Un%dW&Y-sSIExSgjjh+_wU29 zEZRj)#Mua*Mpu0gM=IQ4JQgu;d1Z`rjVbP-RBH{s7|-U68OB_^Eb8+jRjkgPFGY%b zSW)lmE0uA5@nBBR!7>JT)ZKVI!tR&&r}r_y+hi5KZ<2d%ClMnT7Z|k9@2YpR2eZc_BeEovo n^u^%)46>pnCA+$1M9rrB=a=DO(aO45(<#Z0vkc=Kt z?HHPoBl?771ahbO9roz{d;s^1BN-Syj{7~Mg7S{}fB9<(bS!N{IE^XmD!|V~d->mN zf7${j6#x!*D5rV`$UX(QT$s>hQt8{kWMqfz&>c3R8I^RGB%ZNe@?+v+ZtURWF9ymC zl=c(t$BFUHAWVIuQsZNOJ}@c@x5Ldy`CEye`WNX#m2RKlJ)KG+`boSMZWkgcOJCS~ zN^d5q(j4@K2j3fq@ph2JK|k!U^N+d#D(ziC1F1T|GVB!nYOF;|Nl_2u(bxo07^^h@ zmMu*G(*&#zmI<4X{t19zIVM^d0$TODh5{5vbdgM) zOh|i)=r7Mt>9h$__ef^zZsy49M_?0kS=xbANG5BU^BmqCYIm95l`N*x1&L=W&x!j& zY_m3YuhCn`e;0D`u3r}CUF_kV58wCugP!j7jmjU}FT9O{oO}v((X#;gsr`_O{1soz zA37h@=TW|Z(9RE?$@GnexS+WOJG~F!)H%hGYgoc#XAXB6aLi?#wipAsIOBX4WV0Y~ z7&~+xv+8A?6P?yl7CnIVSWYpLi9!>70ZsJ%M{!BN-dsy zBRkAY=Et>LQ=~d{qr)~fI<3ME0-5xKpb%z%1cRsXMvafPXa=KwobB7a6vh*u`o)Z1 z%47JQq`HC*?;zYnSVdPWHM+3E*pyGM;G0PjmGi0HAsS&0*9N-G6{osz?!iB59I15* xT)GaxiCb~0%UsGW4wYjZszVu6iIt~dwbNFWIZ(CNC>M;K3X8oKy!LAK!9Q#gR9*l8 diff --git a/venv/lib/python3.10/site-packages/_pytest/assertion/__pycache__/util.cpython-310.pyc b/venv/lib/python3.10/site-packages/_pytest/assertion/__pycache__/util.cpython-310.pyc index b95c4d5159416a3fbb9c15a4f22886eb92bdfdd8..54978ff6fa4ad162338c361765ce7a372eaefd87 100644 GIT binary patch literal 15404 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 Vm$DJ-5tJ= (3, 12): + from importlib.resources.abc import TraversableResources +else: + from importlib.abc import TraversableResources +if sys.version_info < (3, 11): + from importlib.readers import FileReader +else: + from importlib.resources.readers import FileReader + from _pytest._io.saferepr import DEFAULT_REPR_MAX_SIZE from _pytest._io.saferepr import saferepr +from _pytest._io.saferepr import saferepr_unlimited from _pytest._version import version from _pytest.assertion import util -from _pytest.assertion.util import ( # noqa: F401 - format_explanation as _format_explanation, -) from _pytest.config import Config +from _pytest.fixtures import FixtureFunctionDefinition from _pytest.main import Session from _pytest.pathlib import absolutepath from _pytest.pathlib import fnmatch_ex from _pytest.stash import StashKey + +# fmt: off +from _pytest.assertion.util import format_explanation as _format_explanation # noqa:F401, isort:skip +# fmt:on + if TYPE_CHECKING: from _pytest.assertion import AssertionState -if sys.version_info >= (3, 8): - namedExpr = ast.NamedExpr - astNameConstant = ast.Constant - astStr = ast.Constant - astNum = ast.Constant -else: - namedExpr = ast.Expr - astNameConstant = ast.NameConstant - astStr = ast.Str - astNum = ast.Num - class Sentinel: pass @@ -65,7 +66,7 @@ assertstate_key = StashKey["AssertionState"]() # pytest caches rewritten pycs in pycache dirs PYTEST_TAG = f"{sys.implementation.cache_tag}-pytest-{version}" -PYC_EXT = ".py" + (__debug__ and "c" or "o") +PYC_EXT = ".py" + ((__debug__ and "c") or "o") PYC_TAIL = "." + PYTEST_TAG + PYC_EXT # Special marker that denotes we have just left a scope definition @@ -81,17 +82,17 @@ class AssertionRewritingHook(importlib.abc.MetaPathFinder, importlib.abc.Loader) self.fnpats = config.getini("python_files") except ValueError: self.fnpats = ["test_*.py", "*_test.py"] - self.session: Optional[Session] = None - self._rewritten_names: Dict[str, Path] = {} - self._must_rewrite: Set[str] = set() + 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._marked_for_rewrite_cache: dict[str, bool] = {} self._session_paths_checked = False - def set_session(self, session: Optional[Session]) -> None: + def set_session(self, session: Session | None) -> None: self.session = session self._session_paths_checked = False @@ -101,18 +102,28 @@ class AssertionRewritingHook(importlib.abc.MetaPathFinder, importlib.abc.Loader) def find_spec( self, name: str, - path: Optional[Sequence[Union[str, bytes]]] = None, - target: Optional[types.ModuleType] = None, - ) -> Optional[importlib.machinery.ModuleSpec]: + 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("find_module called for: %s" % name) + 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 @@ -140,7 +151,7 @@ class AssertionRewritingHook(importlib.abc.MetaPathFinder, importlib.abc.Loader) def create_module( self, spec: importlib.machinery.ModuleSpec - ) -> Optional[types.ModuleType]: + ) -> types.ModuleType | None: return None # default behaviour is fine def exec_module(self, module: types.ModuleType) -> None: @@ -185,7 +196,7 @@ class AssertionRewritingHook(importlib.abc.MetaPathFinder, importlib.abc.Loader) state.trace(f"found cached rewritten pyc for {fn}") exec(co, module.__dict__) - def _early_rewrite_bailout(self, name: str, state: "AssertionState") -> bool: + 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 @@ -224,7 +235,7 @@ class AssertionRewritingHook(importlib.abc.MetaPathFinder, importlib.abc.Loader) state.trace(f"early skip of rewriting module: {name}") return True - def _should_rewrite(self, name: str, fn: str, state: "AssertionState") -> bool: + 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}") @@ -245,7 +256,7 @@ class AssertionRewritingHook(importlib.abc.MetaPathFinder, importlib.abc.Loader) return self._is_marked_for_rewrite(name, state) - def _is_marked_for_rewrite(self, name: str, state: "AssertionState") -> bool: + def _is_marked_for_rewrite(self, name: str, state: AssertionState) -> bool: try: return self._marked_for_rewrite_cache[name] except KeyError: @@ -281,31 +292,18 @@ class AssertionRewritingHook(importlib.abc.MetaPathFinder, importlib.abc.Loader) self.config.issue_config_time_warning( PytestAssertRewriteWarning( - "Module already imported so cannot be rewritten: %s" % name + f"Module already imported so cannot be rewritten; {name}" ), stacklevel=5, ) - def get_data(self, pathname: Union[str, bytes]) -> bytes: + 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: # type: ignore - if sys.version_info < (3, 11): - from importlib.readers import FileReader - else: - from importlib.resources.readers import FileReader - - return FileReader( # type:ignore[no-any-return] - types.SimpleNamespace(path=self._rewritten_names[name]) - ) + def get_resource_reader(self, name: str) -> TraversableResources: + return FileReader(types.SimpleNamespace(path=self._rewritten_names[name])) # type: ignore[arg-type] def _write_pyc_fp( @@ -327,7 +325,7 @@ def _write_pyc_fp( def _write_pyc( - state: "AssertionState", + state: AssertionState, co: types.CodeType, source_stat: os.stat_result, pyc: Path, @@ -351,7 +349,7 @@ def _write_pyc( return True -def _rewrite_test(fn: Path, config: Config) -> Tuple[os.stat_result, types.CodeType]: +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() @@ -364,7 +362,7 @@ def _rewrite_test(fn: Path, config: Config) -> Tuple[os.stat_result, types.CodeT def _read_pyc( source: Path, pyc: Path, trace: Callable[[str], None] = lambda x: None -) -> Optional[types.CodeType]: +) -> types.CodeType | None: """Possibly read a pytest pyc containing rewritten code. Return rewritten code if successful or None if not. @@ -384,21 +382,21 @@ def _read_pyc( return None # Check for invalid or out of date pyc file. if len(data) != (16): - trace("_read_pyc(%s): invalid pyc (too short)" % source) + trace(f"_read_pyc({source}): invalid pyc (too short)") return None if data[:4] != importlib.util.MAGIC_NUMBER: - trace("_read_pyc(%s): invalid pyc (bad magic number)" % source) + trace(f"_read_pyc({source}): invalid pyc (bad magic number)") return None if data[4:8] != b"\x00\x00\x00\x00": - trace("_read_pyc(%s): invalid pyc (unsupported flags)" % source) + 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("_read_pyc(%s): out of date" % source) + 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("_read_pyc(%s): invalid pyc (incorrect size)" % source) + trace(f"_read_pyc({source}): invalid pyc (incorrect size)") return None try: co = marshal.load(fp) @@ -406,7 +404,7 @@ def _read_pyc( trace(f"_read_pyc({source}): marshal.load error {e}") return None if not isinstance(co, types.CodeType): - trace("_read_pyc(%s): not a code object" % source) + trace(f"_read_pyc({source}): not a code object") return None return co @@ -414,8 +412,8 @@ def _read_pyc( def rewrite_asserts( mod: ast.Module, source: bytes, - module_path: Optional[str] = None, - config: Optional[Config] = None, + module_path: str | None = None, + config: Config | None = None, ) -> None: """Rewrite the assert statements in mod.""" AssertionRewriter(module_path, config, source).run(mod) @@ -431,13 +429,22 @@ def _saferepr(obj: object) -> str: 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: Optional[Config]) -> Optional[int]: +def _get_maxsize_for_saferepr(config: Config | None) -> int | None: """Get `maxsize` configuration for saferepr based on the given config object.""" - verbosity = config.getoption("verbose") if config is not None else 0 + if config is None: + verbosity = 0 + else: + verbosity = config.get_verbosity(Config.VERBOSITY_ASSERTIONS) if verbosity >= 2: return None if verbosity >= 1: @@ -458,7 +465,7 @@ def _format_assertmsg(obj: object) -> str: # However in either case we want to preserve the newline. replaces = [("\n", "\n~"), ("%", "%%")] if not isinstance(obj, str): - obj = saferepr(obj) + obj = saferepr(obj, _get_maxsize_for_saferepr(util._config)) replaces.append(("\\n", "\n~")) for r1, r2 in replaces: @@ -469,7 +476,8 @@ def _format_assertmsg(obj: object) -> str: def _should_repr_global_name(obj: object) -> bool: if callable(obj): - return False + # For pytest fixtures the __repr__ method provides more information than the function name. + return isinstance(obj, FixtureFunctionDefinition) try: return not hasattr(obj, "__name__") @@ -478,7 +486,7 @@ def _should_repr_global_name(obj: object) -> bool: def _format_boolop(explanations: Iterable[str], is_or: bool) -> str: - explanation = "(" + (is_or and " or " or " and ").join(explanations) + ")" + explanation = "(" + ((is_or and " or ") or " and ").join(explanations) + ")" return explanation.replace("%", "%%") @@ -488,7 +496,7 @@ def _call_reprcompare( expls: Sequence[str], each_obj: Sequence[object], ) -> str: - for i, res, expl in zip(range(len(ops)), results, expls): + for i, res, expl in zip(range(len(ops)), results, expls, strict=True): try: done = not res except Exception: @@ -550,14 +558,14 @@ def traverse_node(node: ast.AST) -> Iterator[ast.AST]: @functools.lru_cache(maxsize=1) -def _get_assertion_exprs(src: bytes) -> Dict[int, str]: +def _get_assertion_exprs(src: bytes) -> dict[int, str]: """Return a mapping from {lineno: "assertion test expression"}.""" - ret: Dict[int, str] = {} + ret: dict[int, str] = {} depth = 0 - lines: List[str] = [] - assert_lineno: Optional[int] = None - seen_lines: Set[int] = set() + 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 @@ -591,7 +599,7 @@ def _get_assertion_exprs(src: bytes) -> Dict[int, str]: # multi-line assert with message elif lineno in seen_lines: lines[-1] = lines[-1][:offset] - # multi line assert with escapd newline before message + # multi line assert with escaped newline before message else: lines.append(line[:offset]) _write_and_reset() @@ -664,7 +672,7 @@ class AssertionRewriter(ast.NodeVisitor): """ def __init__( - self, module_path: Optional[str], config: Optional[Config], source: bytes + self, module_path: str | None, config: Config | None, source: bytes ) -> None: super().__init__() self.module_path = module_path @@ -677,9 +685,9 @@ class AssertionRewriter(ast.NodeVisitor): 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) + 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.""" @@ -694,28 +702,18 @@ class AssertionRewriter(ast.NodeVisitor): 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, astStr) - ): - if sys.version_info >= (3, 8): - doc = item.value.value - else: - doc = item.value.s - 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 + match item: + case ast.Expr(value=ast.Constant(value=str() as doc)) if ( + expect_docstring + ): + if self.is_rewrite_disabled(doc): + return + expect_docstring = False + case ast.ImportFrom(level=0, module="__future__"): + pass + case _: + break pos += 1 # Special case: for a decorated function, set the lineno to that of the # first decorator, not the `def`. Issue #4984. @@ -724,21 +722,15 @@ class AssertionRewriter(ast.NodeVisitor): 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"), - ] + aliases = [ + ast.alias("builtins", "@py_builtins", lineno=lineno, col_offset=0), + ast.alias( + "_pytest.assertion.rewrite", + "@pytest_ar", + lineno=lineno, + col_offset=0, + ), + ] imports = [ ast.Import([alias], lineno=lineno, col_offset=0) for alias in aliases ] @@ -746,10 +738,10 @@ class AssertionRewriter(ast.NodeVisitor): # Collect asserts. self.scope = (mod,) - nodes: List[Union[ast.AST, Sentinel]] = [mod] + nodes: list[ast.AST | Sentinel] = [mod] while nodes: node = nodes.pop() - if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)): + 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: @@ -758,7 +750,7 @@ class AssertionRewriter(ast.NodeVisitor): assert isinstance(node, ast.AST) for name, field in ast.iter_fields(node): if isinstance(field, list): - new: List[ast.AST] = [] + new: list[ast.AST] = [] for i, child in enumerate(field): if isinstance(child, ast.Assert): # Transform assert. @@ -791,7 +783,7 @@ class AssertionRewriter(ast.NodeVisitor): """Give *expr* a name.""" name = self.variable() self.statements.append(ast.Assign([ast.Name(name, ast.Store())], expr)) - return ast.Name(name, ast.Load()) + return ast.copy_location(ast.Name(name, ast.Load()), expr) def display(self, expr: ast.expr) -> ast.expr: """Call saferepr on the expression.""" @@ -830,7 +822,7 @@ class AssertionRewriter(ast.NodeVisitor): to format a string of %-formatted values as added by .explanation_param(). """ - self.explanation_specifiers: Dict[str, ast.expr] = {} + self.explanation_specifiers: dict[str, ast.expr] = {} self.stack.append(self.explanation_specifiers) def pop_format_context(self, expl_expr: ast.expr) -> ast.Name: @@ -844,7 +836,7 @@ class AssertionRewriter(ast.NodeVisitor): current = self.stack.pop() if self.stack: self.explanation_specifiers = self.stack[-1] - keys = [astStr(key) for key in current.keys()] + 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)) @@ -853,13 +845,13 @@ class AssertionRewriter(ast.NodeVisitor): 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]: + 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]: + 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 @@ -868,9 +860,10 @@ class AssertionRewriter(ast.NodeVisitor): the expression is false. """ if isinstance(assert_.test, ast.Tuple) and len(assert_.test.elts) >= 1: - from _pytest.warning_types import PytestAssertRewriteWarning 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( @@ -882,15 +875,15 @@ class AssertionRewriter(ast.NodeVisitor): lineno=assert_.lineno, ) - self.statements: List[ast.stmt] = [] - self.variables: List[str] = [] + 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.format_variables: list[str] = [] - self.stack: List[Dict[str, ast.expr]] = [] - self.expl_stmts: List[ast.stmt] = [] + 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) @@ -898,16 +891,16 @@ class AssertionRewriter(ast.NodeVisitor): negation = ast.UnaryOp(ast.Not(), top_condition) if self.enable_assertion_pass_hook: # Experimental pytest_assertion_pass hook - msg = self.pop_format_context(astStr(explanation)) + msg = self.pop_format_context(ast.Constant(explanation)) # Failed if assert_.msg: assertmsg = self.helper("_format_assertmsg", assert_.msg) gluestr = "\n>assert " else: - assertmsg = astStr("") + assertmsg = ast.Constant("") gluestr = "assert " - err_explanation = ast.BinOp(astStr(gluestr), ast.Add(), msg) + 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) @@ -923,27 +916,27 @@ class AssertionRewriter(ast.NodeVisitor): hook_call_pass = ast.Expr( self.helper( "_call_assertion_pass", - astNum(assert_.lineno), - astStr(orig), + 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], + [*self.expl_stmts, hook_call_pass], [], ) - statements_pass = [hook_impl_test] + 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 = [ + variables: list[ast.expr] = [ ast.Name(name, ast.Store()) for name in self.format_variables ] - clear_format = ast.Assign(variables, astNameConstant(None)) + clear_format = ast.Assign(variables, ast.Constant(None)) self.statements.append(clear_format) else: # Original assertion rewriting @@ -954,9 +947,9 @@ class AssertionRewriter(ast.NodeVisitor): assertmsg = self.helper("_format_assertmsg", assert_.msg) explanation = "\n>assert " + explanation else: - assertmsg = astStr("") + assertmsg = ast.Constant("") explanation = "assert " + explanation - template = ast.BinOp(assertmsg, ast.Add(), astStr(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()) @@ -968,37 +961,40 @@ class AssertionRewriter(ast.NodeVisitor): # 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, astNameConstant(None)) + 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): - ast.copy_location(node, assert_) + 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: namedExpr) -> Tuple[namedExpr, str]: + 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 # type: ignore[attr-defined] - inlocs = ast.Compare(astStr(target_id), [ast.In()], [locs]) + 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), astStr(target_id)) + 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]: + 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(astStr(name.id), [ast.In()], [locs]) + 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), astStr(name.id)) + 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]: + 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()) @@ -1010,60 +1006,57 @@ class AssertionRewriter(ast.NodeVisitor): # Process each operand, short-circuiting if needed. for i, v in enumerate(boolop.values): if i: - fail_inner: List[ast.stmt] = [] + fail_inner: list[ast.stmt] = [] # cond is set in a prior loop iteration below - self.expl_stmts.append(ast.If(cond, fail_inner, [])) # noqa + self.expl_stmts.append(ast.If(cond, fail_inner, [])) # noqa: F821 self.expl_stmts = fail_inner - # Check if the left operand is a namedExpr and the value has already been visited - if ( - isinstance(v, ast.Compare) - and isinstance(v.left, 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 + match v: + # Check if the left operand is an ast.NamedExpr and the value has already been visited + case ast.Compare( + left=ast.NamedExpr(target=ast.Name(id=target_id)) + ) if target_id in [ + e.id for e in boolop.values[:i] if hasattr(e, "id") + ]: + pytest_temp = self.variable() + self.variables_overwrite[self.scope][target_id] = v.left # type:ignore[assignment] + # mypy's false positive, we're checking that the 'target' attribute exists. + v.left.target.id = pytest_temp # type:ignore[attr-defined] 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(astStr(expl)) + 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] = [] + 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, astNum(is_or)) + 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]: + 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.UnaryOp(unary.op, operand_res)) + 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]: + 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.BinOp(left_expr, binop.op, right_expr)) + 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]: + def visit_Call(self, call: ast.Call) -> tuple[ast.Name, str]: new_func, func_expl = self.visit(call.func) arg_expls = [] new_args = [] @@ -1072,19 +1065,16 @@ class AssertionRewriter(ast.NodeVisitor): 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] + 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] + match keyword.value: + case ast.Name(id=id) if id in self.variables_overwrite.get( + self.scope, {} + ): + keyword.value = self.variables_overwrite[self.scope][id] # type:ignore[assignment] res, expl = self.visit(keyword.value) new_kwargs.append(ast.keyword(keyword.arg, res)) if keyword.arg: @@ -1093,70 +1083,68 @@ class AssertionRewriter(ast.NodeVisitor): arg_expls.append("**" + expl) expl = "{}({})".format(func_expl, ", ".join(arg_expls)) - new_call = ast.Call(new_func, new_args, new_kwargs) + 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]: + 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]: + 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.Attribute(value, attr.attr, ast.Load())) + 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]: + 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, namedExpr): - self.variables_overwrite[self.scope][ - comp.left.target.id - ] = comp.left # type:ignore[assignment] + match comp.left: + case ast.Name(id=name_id) if name_id in self.variables_overwrite.get( + self.scope, {} + ): + comp.left = self.variables_overwrite[self.scope][name_id] # type: ignore[assignment] + case ast.NamedExpr(target=ast.Name(id=target_id)): + self.variables_overwrite[self.scope][target_id] = comp.left # type: ignore[assignment] left_res, left_expl = self.visit(comp.left) - if isinstance(comp.left, (ast.Compare, ast.BoolOp)): + 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 = [ast.Name(v, ast.Load()) for v in res_variables] + 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 = [] - syms = [] + it = zip(range(len(comp.ops)), comp.ops, comp.comparators, strict=True) + expls: list[ast.expr] = [] + syms: list[ast.expr] = [] results = [left_res] for i, op, next_operand in it: - if ( - isinstance(next_operand, 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] + match (next_operand, left_res): + case ( + ast.NamedExpr(target=ast.Name(id=target_id)), + ast.Name(id=name_id), + ) if target_id == name_id: + next_operand.target.id = self.variable() + self.variables_overwrite[self.scope][name_id] = next_operand # type: ignore[assignment] + next_res, next_expl = self.visit(next_operand) - if isinstance(next_operand, (ast.Compare, ast.BoolOp)): + if isinstance(next_operand, ast.Compare | ast.BoolOp): next_expl = f"({next_expl})" results.append(next_res) sym = BINOP_MAP[op.__class__] - syms.append(astStr(sym)) + syms.append(ast.Constant(sym)) expl = f"{left_expl} {sym} {next_expl}" - expls.append(astStr(expl)) - res_expr = ast.Compare(left_res, [op], [next_res]) + 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. @@ -1191,7 +1179,10 @@ def try_makedirs(cache_dir: Path) -> bool: return False except OSError as e: # as of now, EROFS doesn't have an equivalent OSError-subclass - if e.errno == errno.EROFS: + # + # 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 @@ -1199,7 +1190,7 @@ def try_makedirs(cache_dir: Path) -> bool: def get_cache_dir(file_path: Path) -> Path: """Return the cache directory to write .pyc files for the given .py file path.""" - if sys.version_info >= (3, 8) and sys.pycache_prefix: + if sys.pycache_prefix: # given: # prefix = '/tmp/pycs' # path = '/home/user/proj/test_app.py' diff --git a/venv/lib/python3.10/site-packages/_pytest/assertion/truncate.py b/venv/lib/python3.10/site-packages/_pytest/assertion/truncate.py index dfd6f65..5820e6e 100644 --- a/venv/lib/python3.10/site-packages/_pytest/assertion/truncate.py +++ b/venv/lib/python3.10/site-packages/_pytest/assertion/truncate.py @@ -1,51 +1,65 @@ """Utilities for truncating assertion output. Current default behaviour is to truncate assertion explanations at -~8 terminal lines, unless running in "-vv" mode or running on CI. +terminal lines, unless running with an assertions verbosity level of at least 2 or running on CI. """ -from typing import List -from typing import Optional -from _pytest.assertion import util +from __future__ import annotations + +from _pytest.compat import running_on_ci +from _pytest.config import Config from _pytest.nodes import Item DEFAULT_MAX_LINES = 8 -DEFAULT_MAX_CHARS = 8 * 80 +DEFAULT_MAX_CHARS = DEFAULT_MAX_LINES * 80 USAGE_MSG = "use '-vv' to show" -def truncate_if_required( - explanation: List[str], item: Item, max_length: Optional[int] = None -) -> List[str]: +def truncate_if_required(explanation: list[str], item: Item) -> list[str]: """Truncate this assertion explanation if the given test item is eligible.""" - if _should_truncate_item(item): - return _truncate_explanation(explanation) + should_truncate, max_lines, max_chars = _get_truncation_parameters(item) + if should_truncate: + return _truncate_explanation( + explanation, + max_lines=max_lines, + max_chars=max_chars, + ) return explanation -def _should_truncate_item(item: Item) -> bool: - """Whether or not this test item is eligible for truncation.""" - verbose = item.config.option.verbose - return verbose < 2 and not util.running_on_ci() +def _get_truncation_parameters(item: Item) -> tuple[bool, int, int]: + """Return the truncation parameters related to the given item, as (should truncate, max lines, max chars).""" + # We do not need to truncate if one of conditions is met: + # 1. Verbosity level is 2 or more; + # 2. Test is being run in CI environment; + # 3. Both truncation_limit_lines and truncation_limit_chars + # .ini parameters are set to 0 explicitly. + max_lines = item.config.getini("truncation_limit_lines") + max_lines = int(max_lines if max_lines is not None else DEFAULT_MAX_LINES) + + max_chars = item.config.getini("truncation_limit_chars") + max_chars = int(max_chars if max_chars is not None else DEFAULT_MAX_CHARS) + + verbose = item.config.get_verbosity(Config.VERBOSITY_ASSERTIONS) + + should_truncate = verbose < 2 and not running_on_ci() + should_truncate = should_truncate and (max_lines > 0 or max_chars > 0) + + return should_truncate, max_lines, max_chars def _truncate_explanation( - input_lines: List[str], - max_lines: Optional[int] = None, - max_chars: Optional[int] = None, -) -> List[str]: + input_lines: list[str], + max_lines: int, + max_chars: int, +) -> 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 + Truncates to either max_lines, or max_chars - 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 @@ -70,16 +84,23 @@ def _truncate_explanation( ): return input_lines # Truncate first to max_lines, and then truncate to max_chars if necessary - truncated_explanation = input_lines[:max_lines] + if max_lines > 0: + truncated_explanation = input_lines[:max_lines] + else: + truncated_explanation = input_lines truncated_char = True # We reevaluate the need to truncate chars following removal of some lines - if len("".join(truncated_explanation)) > tolerable_max_chars: + if len("".join(truncated_explanation)) > tolerable_max_chars and max_chars > 0: truncated_explanation = _truncate_by_char_count( truncated_explanation, max_chars ) else: truncated_char = False + if truncated_explanation == input_lines: + # No truncation happened, so we do not need to add any explanations + return truncated_explanation + truncated_line_count = len(input_lines) - len(truncated_explanation) if truncated_explanation[-1]: # Add ellipsis and take into account part-truncated final line @@ -90,14 +111,15 @@ def _truncate_explanation( else: # Add proper ellipsis when we were able to fit a full line exactly truncated_explanation[-1] = "..." - return truncated_explanation + [ + 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]: +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): diff --git a/venv/lib/python3.10/site-packages/_pytest/assertion/util.py b/venv/lib/python3.10/site-packages/_pytest/assertion/util.py index fc5dfdb..f35d83a 100644 --- a/venv/lib/python3.10/site-packages/_pytest/assertion/util.py +++ b/venv/lib/python3.10/site-packages/_pytest/assertion/util.py @@ -1,36 +1,54 @@ +# mypy: allow-untyped-defs """Utilities for assertion debugging.""" + +from __future__ import annotations + import collections.abc -import os +from collections.abc import Callable +from collections.abc import Iterable +from collections.abc import Mapping +from collections.abc import Sequence +from collections.abc import Set as AbstractSet import pprint -from typing import AbstractSet from typing import Any -from typing import Callable -from typing import Iterable -from typing import List -from typing import Mapping -from typing import Optional -from typing import Sequence +from typing import Literal +from typing import Protocol from unicodedata import normalize -import _pytest._code from _pytest import outcomes -from _pytest._io.saferepr import _pformat_dispatch +import _pytest._code +from _pytest._io.pprint import PrettyPrinter from _pytest._io.saferepr import saferepr from _pytest._io.saferepr import saferepr_unlimited +from _pytest.compat import running_on_ci 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: Optional[Callable[[str, object, object], Optional[str]]] = None +_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: Optional[Callable[[int, str, str], None]] = None +_assertion_pass: Callable[[int, str, str], None] | None = None # Config object which is assigned during pytest_runtest_protocol. -_config: Optional[Config] = None +_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 dummy_highlighter(source: str, lexer: Literal["diff", "python"] = "python") -> str: + """Dummy highlighter that returns the text unprocessed. + + Needed for _notin_text, as the diff gets post-processed to only show the "+" part. + """ + return source def format_explanation(explanation: str) -> str: @@ -48,7 +66,7 @@ def format_explanation(explanation: str) -> str: return "\n".join(result) -def _split_explanation(explanation: str) -> List[str]: +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~'. @@ -65,7 +83,7 @@ def _split_explanation(explanation: str) -> List[str]: return lines -def _format_lines(lines: Sequence[str]) -> List[str]: +def _format_lines(lines: Sequence[str]) -> list[str]: """Format the individual lines. This will replace the '{', '}' and '~' characters of our mini formatting @@ -113,7 +131,7 @@ def isdict(x: Any) -> bool: def isset(x: Any) -> bool: - return isinstance(x, (set, frozenset)) + return isinstance(x, set | frozenset) def isnamedtuple(obj: Any) -> bool: @@ -132,7 +150,7 @@ def isiterable(obj: Any) -> bool: try: iter(obj) return not istext(obj) - except TypeError: + except Exception: return False @@ -151,7 +169,7 @@ def has_default_eq( code_filename = obj.__eq__.__code__.co_filename if isattrs(obj): - return "attrs generated eq" in code_filename + return "attrs generated " in code_filename return code_filename == "" # data class return True @@ -159,9 +177,9 @@ def has_default_eq( def assertrepr_compare( config, op: str, left: Any, right: Any, use_ascii: bool = False -) -> Optional[List[str]]: +) -> list[str] | None: """Return specialised explanations for some operators/operands.""" - verbose = config.getoption("verbose") + 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. @@ -185,34 +203,54 @@ def assertrepr_compare( 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, verbose) + 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 = [ - "(pytest_assertion plugin: representation of details failed: {}.".format( - _pytest._code.ExceptionInfo.from_current()._getreprcrash() - ), + f"(pytest_assertion plugin: representation of details failed: {repr_crash}.", " Probably an object has a faulty __repr__.)", ] if not explanation: return None - return [summary] + explanation + if explanation[0] != "": + explanation = ["", *explanation] + return [summary, *explanation] -def _compare_eq_any(left: Any, right: Any, verbose: int = 0) -> List[str]: +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) + explanation = _diff_text(left, right, highlighter, verbose) else: from _pytest.python_api import ApproxBase @@ -222,29 +260,31 @@ def _compare_eq_any(left: Any, right: Any, verbose: int = 0) -> List[str]: other_side = right if isinstance(left, ApproxBase) else left explanation = approx_side._repr_compare(other_side) - elif type(left) == type(right) and ( + 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, verbose) + explanation = _compare_eq_cls(left, right, highlighter, verbose) elif issequence(left) and issequence(right): - explanation = _compare_eq_sequence(left, right, verbose) + explanation = _compare_eq_sequence(left, right, highlighter, verbose) elif isset(left) and isset(right): - explanation = _compare_eq_set(left, right, verbose) + explanation = _compare_eq_set(left, right, highlighter, verbose) elif isdict(left) and isdict(right): - explanation = _compare_eq_dict(left, right, verbose) + explanation = _compare_eq_dict(left, right, highlighter, verbose) if isiterable(left) and isiterable(right): - expl = _compare_eq_iterable(left, right, verbose) + 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]: +def _diff_text( + left: str, right: str, highlighter: _HighlightFunc, verbose: int = 0 +) -> list[str]: """Return the explanation for the diff between text. Unless --verbose is used this will skip leading and trailing @@ -252,7 +292,7 @@ def _diff_text(left: str, right: str, verbose: int = 0) -> List[str]: """ from difflib import ndiff - explanation: List[str] = [] + explanation: list[str] = [] if verbose < 1: i = 0 # just in case left or right has zero length @@ -262,7 +302,7 @@ def _diff_text(left: str, right: str, verbose: int = 0) -> List[str]: if i > 42: i -= 10 # Provide some context explanation = [ - "Skipping %s identical leading characters in diff, use -v to show" % i + f"Skipping {i} identical leading characters in diff, use -v to show" ] left = left[i:] right = right[i:] @@ -273,8 +313,8 @@ def _diff_text(left: str, right: str, verbose: int = 0) -> List[str]: if i > 42: i -= 10 # Provide some context explanation += [ - "Skipping {} identical trailing " - "characters in diff, use -v to show".format(i) + f"Skipping {i} identical trailing " + "characters in diff, use -v to show" ] left = left[:-i] right = right[:-i] @@ -285,61 +325,55 @@ def _diff_text(left: str, right: str, verbose: int = 0) -> List[str]: 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)) - ] + explanation.extend( + highlighter( + "\n".join( + line.strip("\n") + for line in ndiff(right.splitlines(keepends), left.splitlines(keepends)) + ), + lexer="diff", + ).splitlines() + ) return explanation -def _surrounding_parens_on_own_lines(lines: List[str]) -> None: - """Move opening/closing parenthesis/bracket to own lines.""" - opening = lines[0][:1] - if opening in ["(", "[", "{"]: - lines[0] = " " + lines[0][1:] - lines[:] = [opening] + lines - closing = lines[-1][-1:] - if closing in [")", "]", "}"]: - lines[-1] = lines[-1][:-1] + "," - lines[:] = lines + [closing] - - def _compare_eq_iterable( - left: Iterable[Any], right: Iterable[Any], verbose: int = 0 -) -> List[str]: + 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 = pprint.pformat(left).splitlines() - right_formatting = pprint.pformat(right).splitlines() + left_formatting = PrettyPrinter().pformat(left).splitlines() + right_formatting = PrettyPrinter().pformat(right).splitlines() - # Re-format for different output lengths. - lines_left = len(left_formatting) - lines_right = len(right_formatting) - if lines_left != lines_right: - left_formatting = _pformat_dispatch(left).splitlines() - right_formatting = _pformat_dispatch(right).splitlines() - - if lines_left > 1 or lines_right > 1: - _surrounding_parens_on_own_lines(left_formatting) - _surrounding_parens_on_own_lines(right_formatting) - - explanation = ["Full diff:"] + explanation = ["", "Full diff:"] # "right" is the expected base against which we compare "left", # see https://github.com/pytest-dev/pytest/issues/3333 explanation.extend( - line.rstrip() for line in difflib.ndiff(right_formatting, left_formatting) + 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], verbose: int = 0 -) -> List[str]: + 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] = [] + explanation: list[str] = [] len_left = len(left) len_right = len(right) for i in range(min(len_left, len_right)): @@ -359,7 +393,10 @@ def _compare_eq_sequence( left_value = left[i] right_value = right[i] - explanation += [f"At index {i} diff: {left_value!r} != {right_value!r}"] + explanation.append( + f"At index {i} diff:" + f" {highlighter(repr(left_value))} != {highlighter(repr(right_value))}" + ) break if comparing_bytes: @@ -379,74 +416,134 @@ def _compare_eq_sequence( extra = saferepr(right[len_left]) if len_diff == 1: - explanation += [f"{dir_with_more} contains one more item: {extra}"] + 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, extra) + f"{dir_with_more} contains {len_diff} more items, first extra item: {highlighter(extra)}" ] return explanation def _compare_eq_set( - left: AbstractSet[Any], right: AbstractSet[Any], verbose: int = 0 -) -> List[str]: + left: AbstractSet[Any], + right: AbstractSet[Any], + highlighter: _HighlightFunc, + verbose: int = 0, +) -> list[str]: explanation = [] - diff_left = left - right - diff_right = right - left - if diff_left: - explanation.append("Extra items in the left set:") - for item in diff_left: - explanation.append(saferepr(item)) - if diff_right: - explanation.append("Extra items in the right set:") - for item in diff_right: - explanation.append(saferepr(item)) + 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], verbose: int = 0 -) -> List[str]: - explanation: List[str] = [] + 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 += ["Omitting %s identical items, use -vv to show" % len(same)] + explanation += [f"Omitting {len(same)} identical items, use -vv to show"] elif same: explanation += ["Common items:"] - explanation += pprint.pformat(same).splitlines() + 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 += [saferepr({k: left[k]}) + " != " + saferepr({k: right[k]})] + 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") + f"Left contains {len_extra_left} more item{'' if len_extra_left == 1 else 's'}:" ) explanation.extend( - pprint.pformat({k: left[k] for k in extra_left}).splitlines() + 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") + f"Right contains {len_extra_right} more item{'' if len_extra_right == 1 else 's'}:" ) explanation.extend( - pprint.pformat({k: right[k] for k in extra_right}).splitlines() + highlighter(pprint.pformat({k: right[k] for k in extra_right})).splitlines() ) return explanation -def _compare_eq_cls(left: Any, right: Any, verbose: int) -> List[str]: +def _compare_eq_cls( + left: Any, right: Any, highlighter: _HighlightFunc, verbose: int +) -> list[str]: if not has_default_eq(left): return [] if isdatacls(left): @@ -475,35 +572,37 @@ def _compare_eq_cls(left: Any, right: Any, verbose: int) -> List[str]: if same or diff: explanation += [""] if same and verbose < 2: - explanation.append("Omitting %s identical items, use -vv to show" % len(same)) + explanation.append(f"Omitting {len(same)} identical items, use -vv to show") elif same: explanation += ["Matching attributes:"] - explanation += pprint.pformat(same).splitlines() + explanation += highlighter(pprint.pformat(same)).splitlines() if diff: explanation += ["Differing attributes:"] - explanation += pprint.pformat(diff).splitlines() + explanation += highlighter(pprint.pformat(diff)).splitlines() for field in diff: field_left = getattr(left, field) field_right = getattr(right, field) explanation += [ "", - "Drill down into differing attribute %s:" % field, - ("%s%s: %r != %r") % (indent, field, field_left, field_right), + 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, verbose) + 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]: +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 = ["%s is contained here:" % saferepr(term, maxsize=42)] + diff = _diff_text(text, correct_text, dummy_highlighter, verbose) + newdiff = [f"{saferepr(term, maxsize=42)} is contained here:"] for line in diff: if line.startswith("Skipping"): continue @@ -514,9 +613,3 @@ def _notin_text(term: str, text: str, verbose: int = 0) -> List[str]: 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/python3.10/site-packages/_pytest/cacheprovider.py b/venv/lib/python3.10/site-packages/_pytest/cacheprovider.py index 1ecb865..4383f10 100644 --- a/venv/lib/python3.10/site-packages/_pytest/cacheprovider.py +++ b/venv/lib/python3.10/site-packages/_pytest/cacheprovider.py @@ -1,24 +1,25 @@ +# 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 + +from collections.abc import Generator +from collections.abc import Iterable import dataclasses +import errno import json import os from pathlib import Path -from typing import Dict -from typing import Generator -from typing import Iterable -from typing import List -from typing import Optional -from typing import Set -from typing import Union +import tempfile +from typing import final 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.compat import final from _pytest.config import Config from _pytest.config import ExitCode from _pytest.config import hookimpl @@ -27,10 +28,11 @@ 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.python import Package from _pytest.reports import TestReport + README_CONTENT = """\ # pytest cache directory # @@ -72,7 +74,7 @@ class Cache: self._config = config @classmethod - def for_config(cls, config: Config, *, _ispytest: bool = False) -> "Cache": + def for_config(cls, config: Config, *, _ispytest: bool = False) -> Cache: """Create the Cache instance for a Config. :meta private: @@ -111,6 +113,7 @@ class Cache: """ check_ispytest(_ispytest) import warnings + from _pytest.warning_types import PytestCacheWarning warnings.warn( @@ -119,6 +122,10 @@ class Cache: 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. @@ -137,7 +144,7 @@ class Cache: if len(path.parts) > 1: raise ValueError("name is not allowed to contain path separators") res = self._cachedir.joinpath(self._CACHE_PREFIX_DIRS, path) - res.mkdir(exist_ok=True, parents=True) + self._mkdir(res) return res def _getvaluepath(self, key: str) -> Path: @@ -174,19 +181,13 @@ class Cache: """ path = self._getvaluepath(key) try: - if path.parent.is_dir(): - cache_dir_exists_already = True - else: - cache_dir_exists_already = self._cachedir.exists() - path.parent.mkdir(exist_ok=True, parents=True) + self._mkdir(path.parent) except OSError as exc: self.warn( f"could not create cache path {path}: {exc}", _ispytest=True, ) return - if not cache_dir_exists_already: - self._ensure_supporting_files() data = json.dumps(value, ensure_ascii=False, indent=2) try: f = path.open("w", encoding="UTF-8") @@ -199,60 +200,85 @@ class Cache: with f: f.write(data) - def _ensure_supporting_files(self) -> None: - """Create supporting files in the cache dir that are not really part of the cache.""" - readme_path = self._cachedir / "README.md" - readme_path.write_text(README_CONTENT, encoding="UTF-8") + def _ensure_cache_dir_and_supporting_files(self) -> None: + """Create the cache dir and its supporting files.""" + if self._cachedir.is_dir(): + return - gitignore_path = self._cachedir.joinpath(".gitignore") - msg = "# Created by pytest automatically.\n*\n" - gitignore_path.write_text(msg, encoding="UTF-8") + 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) - cachedir_tag_path = self._cachedir.joinpath("CACHEDIR.TAG") - cachedir_tag_path.write_bytes(CACHEDIR_TAG_CONTENT) + # 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: + def __init__(self, lfplugin: LFPlugin) -> None: self.lfplugin = lfplugin self._collected_at_least_one_failure = False - @hookimpl(hookwrapper=True) - def pytest_make_collect_report(self, collector: nodes.Collector): - if isinstance(collector, (Session, Package)): - out = yield - res: CollectReport = out.get_result() - + @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 priorize last failed. - def sort_key(node: Union[nodes.Item, nodes.Collector]) -> bool: - # Package.path is the __init__.py file, we need the directory. - if isinstance(node, Package): - path = node.path.parent - else: - path = node.path - return path in lf_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, ) - return elif isinstance(collector, File): if collector.path in self.lfplugin._last_failed_paths: - out = yield - res = out.get_result() 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 + return res self.lfplugin.config.pluginmanager.register( LFPluginCollSkipfiles(self.lfplugin), "lfplugin-collskip" ) @@ -268,21 +294,19 @@ class LFPluginCollWrapper: # Keep all sub-collectors. or isinstance(x, nodes.Collector) ] - return - yield + + return res class LFPluginCollSkipfiles: - def __init__(self, lfplugin: "LFPlugin") -> None: + def __init__(self, lfplugin: LFPlugin) -> None: self.lfplugin = lfplugin @hookimpl def pytest_make_collect_report( self, collector: nodes.Collector - ) -> Optional[CollectReport]: - # Packages are Files, but we only want to skip test-bearing Files, - # so don't filter Packages. - if isinstance(collector, File) and not isinstance(collector, Package): + ) -> CollectReport | None: + if isinstance(collector, File): if collector.path not in self.lfplugin._last_failed_paths: self.lfplugin._skipped_files += 1 @@ -300,9 +324,9 @@ class LFPlugin: 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: Optional[int] = None - self._report_status: Optional[str] = None + 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"): @@ -311,7 +335,7 @@ class LFPlugin: LFPluginCollWrapper(self), "lfplugin-collwrapper" ) - def get_last_failed_paths(self) -> Set[Path]: + 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 @@ -322,9 +346,9 @@ class LFPlugin: result.update(path.parents) return {x for x in result if x.exists()} - def pytest_report_collectionfinish(self) -> Optional[str]: - if self.active and self.config.getoption("verbose") >= 0: - return "run-last-failure: %s" % self._report_status + 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: @@ -342,14 +366,14 @@ class LFPlugin: else: self.lastfailed[report.nodeid] = True - @hookimpl(hookwrapper=True, tryfirst=True) + @hookimpl(wrapper=True, tryfirst=True) def pytest_collection_modifyitems( - self, config: Config, items: List[nodes.Item] - ) -> Generator[None, None, None]: - yield + self, config: Config, items: list[nodes.Item] + ) -> Generator[None]: + res = yield if not self.active: - return + return res if self.lastfailed: previously_failed = [] @@ -364,8 +388,8 @@ class LFPlugin: 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), + self._report_status = ( + f"{len(self.lastfailed)} known failures not in selected tests" ) else: if self.config.getoption("lf"): @@ -376,15 +400,13 @@ class LFPlugin: noun = "failure" if self._previously_failed_count == 1 else "failures" suffix = " first" if self.config.getoption("failedfirst") else "" - self._report_status = "rerun previous {count} {noun}{suffix}".format( - count=self._previously_failed_count, suffix=suffix, noun=noun + 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 += " (skipped {files} {files_noun})".format( - files=self._skipped_files, files_noun=files_noun - ) + 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": @@ -394,6 +416,8 @@ class LFPlugin: 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"): @@ -414,15 +438,13 @@ class NFPlugin: assert config.cache is not None self.cached_nodeids = set(config.cache.get("cache/nodeids", [])) - @hookimpl(hookwrapper=True, tryfirst=True) - def pytest_collection_modifyitems( - self, items: List[nodes.Item] - ) -> Generator[None, None, None]: - yield + @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] = {} + 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 @@ -436,8 +458,10 @@ class NFPlugin: else: self.cached_nodeids.update(item.nodeid for item in items) - 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) # type: ignore[no-any-return] + 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 @@ -452,14 +476,17 @@ class NFPlugin: def pytest_addoption(parser: Parser) -> None: + """Add command-line options for cache functionality. + + :param parser: Parser object to add command-line options to. + """ 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)", + help="Rerun only the tests that failed at the last run (or all if none failed)", ) group.addoption( "--ff", @@ -513,7 +540,7 @@ def pytest_addoption(parser: Parser) -> None: ) -def pytest_cmdline_main(config: Config) -> Optional[Union[int, ExitCode]]: +def pytest_cmdline_main(config: Config) -> int | ExitCode | None: if config.option.cacheshow and not config.option.help: from _pytest.main import wrap_session @@ -523,6 +550,13 @@ def pytest_cmdline_main(config: Config) -> Optional[Union[int, ExitCode]]: @hookimpl(tryfirst=True) def pytest_configure(config: Config) -> None: + """Configure cache system and register related plugins. + + Creates the Cache instance and registers the last-failed (LFPlugin) + and new-first (NFPlugin) plugins with the plugin manager. + + :param config: pytest configuration object. + """ config.cache = Cache.for_config(config, _ispytest=True) config.pluginmanager.register(LFPlugin(config), "lfplugin") config.pluginmanager.register(NFPlugin(config), "nfplugin") @@ -544,7 +578,7 @@ def cache(request: FixtureRequest) -> Cache: return request.config.cache -def pytest_report_header(config: Config) -> Optional[str]: +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 @@ -561,6 +595,16 @@ def pytest_report_header(config: Config) -> Optional[str]: def cacheshow(config: Config, session: Session) -> int: + """Display cache contents when --cache-show is used. + + Shows cached values and directories matching the specified glob pattern + (default: '*'). Displays cache location, cached test results, and + any cached directories created by plugins. + + :param config: pytest configuration object. + :param session: pytest session object. + :returns: Exit code (0 for success). + """ from pprint import pformat assert config.cache is not None @@ -578,25 +622,25 @@ def cacheshow(config: Config, session: Session) -> int: dummy = object() basedir = config.cache._cachedir vdir = basedir / Cache._CACHE_PREFIX_VALUES - tw.sep("-", "cache values for %r" % glob) + 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("%s contains unreadable content, will be ignored" % key) + tw.line(f"{key} contains unreadable content, will be ignored") else: - tw.line("%s contains:" % key) + 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("-", "cache directories for %r" % 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}") + tw.line(f"{key} is a file of length {p.stat().st_size}") return 0 diff --git a/venv/lib/python3.10/site-packages/_pytest/capture.py b/venv/lib/python3.10/site-packages/_pytest/capture.py index a8ca086..6d98676 100644 --- a/venv/lib/python3.10/site-packages/_pytest/capture.py +++ b/venv/lib/python3.10/site-packages/_pytest/capture.py @@ -1,30 +1,36 @@ +# mypy: allow-untyped-defs """Per-test stdout/stderr capturing mechanism.""" + +from __future__ import annotations + import abc import collections +from collections.abc import Generator +from collections.abc import Iterable +from collections.abc import Iterator import contextlib import io +from io import UnsupportedOperation import os import sys -from io import UnsupportedOperation from tempfile import TemporaryFile from types import TracebackType from typing import Any from typing import AnyStr from typing import BinaryIO -from typing import Generator +from typing import cast +from typing import Final +from typing import final from typing import Generic -from typing import Iterable -from typing import Iterator -from typing import List +from typing import Literal from typing import NamedTuple -from typing import Optional from typing import TextIO -from typing import Tuple -from typing import Type from typing import TYPE_CHECKING -from typing import Union -from _pytest.compat import final + +if TYPE_CHECKING: + from typing_extensions import Self + from _pytest.config import Config from _pytest.config import hookimpl from _pytest.config.argparsing import Parser @@ -34,17 +40,15 @@ 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 -if TYPE_CHECKING: - from typing_extensions import Final - from typing_extensions import Literal - _CaptureMethod = Literal["fd", "sys", "no", "tee-sys"] +_CaptureMethod = Literal["fd", "sys", "no", "tee-sys"] def pytest_addoption(parser: Parser) -> None: group = parser.getgroup("general") - group._addoption( + group.addoption( "--capture", action="store", default="fd", @@ -52,7 +56,7 @@ def pytest_addoption(parser: Parser) -> None: choices=["fd", "sys", "no", "tee-sys"], help="Per-test capturing method: one of fd|sys|no|tee-sys", ) - group._addoption( + group._addoption( # private to use reserved lower-case short option "-s", action="store_const", const="no", @@ -76,6 +80,23 @@ def _colorama_workaround() -> None: 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. @@ -104,17 +125,16 @@ def _windowsconsoleio_workaround(stream: TextIO) -> None: return # Bail out if ``stream`` doesn't seem like a proper ``io`` stream (#2666). - if not hasattr(stream, "buffer"): # type: ignore[unreachable] + if not hasattr(stream, "buffer"): # type: ignore[unreachable,unused-ignore] return - buffered = hasattr(stream.buffer, "raw") - raw_stdout = stream.buffer.raw if buffered else stream.buffer # type: ignore[attr-defined] + raw_stdout = stream.buffer.raw if hasattr(stream.buffer, "raw") else stream.buffer - if not isinstance(raw_stdout, io._WindowsConsoleIO): # type: ignore[attr-defined] + if not isinstance(raw_stdout, io._WindowsConsoleIO): # type: ignore[attr-defined,unused-ignore] return def _reopen_stdio(f, mode): - if not buffered and mode[0] == "w": + if not hasattr(stream.buffer, "raw") and mode[0] == "w": buffering = 0 else: buffering = -1 @@ -132,12 +152,13 @@ def _windowsconsoleio_workaround(stream: TextIO) -> None: sys.stderr = _reopen_stdio(sys.stderr, "wb") -@hookimpl(hookwrapper=True) -def pytest_load_initial_conftests(early_config: Config): +@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") @@ -147,12 +168,16 @@ def pytest_load_initial_conftests(early_config: Config): # Finally trigger conftest loading but while capturing (issue #93). capman.start_global_capturing() - outcome = yield - capman.suspend_global_capture() - if outcome.excinfo is not None: + 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. @@ -171,7 +196,8 @@ class EncodedFile(io.TextIOWrapper): 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", "") + assert hasattr(self.buffer, "mode") + return cast(str, self.buffer.mode.replace("b", "")) class CaptureIO(io.TextIOWrapper): @@ -196,6 +222,7 @@ class TeeCaptureIO(CaptureIO): 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: @@ -208,7 +235,7 @@ class DontReadFromInput(TextIO): def __next__(self) -> str: return self.readline() - def readlines(self, hint: Optional[int] = -1) -> List[str]: + def readlines(self, hint: int | None = -1) -> list[str]: raise OSError( "pytest: reading from stdin while output is captured! Consider using `-s`." ) @@ -240,7 +267,7 @@ class DontReadFromInput(TextIO): def tell(self) -> int: raise UnsupportedOperation("redirected stdin is pseudofile, has no tell()") - def truncate(self, size: Optional[int] = None) -> int: + def truncate(self, size: int | None = None) -> int: raise UnsupportedOperation("cannot truncate stdin") def write(self, data: str) -> int: @@ -252,14 +279,14 @@ class DontReadFromInput(TextIO): def writable(self) -> bool: return False - def __enter__(self) -> "DontReadFromInput": + def __enter__(self) -> Self: return self def __exit__( self, - type: Optional[Type[BaseException]], - value: Optional[BaseException], - traceback: Optional[TracebackType], + type: type[BaseException] | None, + value: BaseException | None, + traceback: TracebackType | None, ) -> None: pass @@ -334,7 +361,7 @@ class NoCapture(CaptureBase[str]): class SysCaptureBase(CaptureBase[AnyStr]): def __init__( - self, fd: int, tmpfile: Optional[TextIO] = None, *, tee: bool = False + self, fd: int, tmpfile: TextIO | None = None, *, tee: bool = False ) -> None: name = patchsysdict[fd] self._old: TextIO = getattr(sys, name) @@ -351,7 +378,7 @@ class SysCaptureBase(CaptureBase[AnyStr]): return "<{} {} _old={} _state={!r} tmpfile={!r}>".format( class_name, self.name, - hasattr(self, "_old") and repr(self._old) or "", + (hasattr(self, "_old") and repr(self._old)) or "", self._state, self.tmpfile, ) @@ -360,16 +387,16 @@ class SysCaptureBase(CaptureBase[AnyStr]): return "<{} {} _old={} _state={!r} tmpfile={!r}>".format( self.__class__.__name__, self.name, - hasattr(self, "_old") and repr(self._old) or "", + (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 _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: @@ -452,7 +479,7 @@ class FDCaptureBase(CaptureBase[AnyStr]): # 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: Optional[int] = os.open(os.devnull, os.O_RDWR) + self.targetfd_invalid: int | None = os.open(os.devnull, os.O_RDWR) os.dup2(self.targetfd_invalid, targetfd) else: self.targetfd_invalid = None @@ -477,19 +504,16 @@ class FDCaptureBase(CaptureBase[AnyStr]): self._state = "initialized" def __repr__(self) -> str: - return "<{} {} oldfd={} _state={!r} tmpfile={!r}>".format( - self.__class__.__name__, - self.targetfd, - self.targetfd_save, - self._state, - self.tmpfile, + 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 _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: @@ -546,7 +570,7 @@ class FDCaptureBinary(FDCaptureBase[bytes]): res = self.tmpfile.buffer.read() self.tmpfile.seek(0) self.tmpfile.truncate() - return res + return res # type: ignore[return-value] def writeorg(self, data: bytes) -> None: """Write to original file descriptor.""" @@ -585,7 +609,7 @@ if sys.version_info >= (3, 11) or TYPE_CHECKING: @final class CaptureResult(NamedTuple, Generic[AnyStr]): - """The result of :method:`CaptureFixture.readouterr`.""" + """The result of :method:`caplog.readouterr() `.""" out: AnyStr err: AnyStr @@ -593,9 +617,10 @@ if sys.version_info >= (3, 11) or TYPE_CHECKING: else: class CaptureResult( - collections.namedtuple("CaptureResult", ["out", "err"]), Generic[AnyStr] + collections.namedtuple("CaptureResult", ["out", "err"]), # noqa: PYI024 + Generic[AnyStr], ): - """The result of :method:`CaptureFixture.readouterr`.""" + """The result of :method:`caplog.readouterr() `.""" __slots__ = () @@ -606,21 +631,18 @@ class MultiCapture(Generic[AnyStr]): def __init__( self, - in_: Optional[CaptureBase[AnyStr]], - out: Optional[CaptureBase[AnyStr]], - err: Optional[CaptureBase[AnyStr]], + in_: CaptureBase[AnyStr] | None, + out: CaptureBase[AnyStr] | None, + err: CaptureBase[AnyStr] | None, ) -> None: - self.in_: Optional[CaptureBase[AnyStr]] = in_ - self.out: Optional[CaptureBase[AnyStr]] = out - self.err: Optional[CaptureBase[AnyStr]] = err + self.in_: CaptureBase[AnyStr] | None = in_ + self.out: CaptureBase[AnyStr] | None = out + self.err: CaptureBase[AnyStr] | None = err def __repr__(self) -> str: - return "".format( - self.out, - self.err, - self.in_, - self._state, - self._in_suspended, + return ( + f"" ) def start_capturing(self) -> None: @@ -632,7 +654,7 @@ class MultiCapture(Generic[AnyStr]): if self.err: self.err.start() - def pop_outerr_to_orig(self) -> Tuple[AnyStr, AnyStr]: + 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: @@ -687,7 +709,7 @@ class MultiCapture(Generic[AnyStr]): return CaptureResult(out, err) # type: ignore[arg-type] -def _get_multicapture(method: "_CaptureMethod") -> MultiCapture[str]: +def _get_multicapture(method: _CaptureMethod) -> MultiCapture[str]: if method == "fd": return MultiCapture(in_=FDCapture(0), out=FDCapture(1), err=FDCapture(2)) elif method == "sys": @@ -723,21 +745,22 @@ class CaptureManager: needed to ensure the fixtures take precedence over the global capture. """ - def __init__(self, method: "_CaptureMethod") -> None: + def __init__(self, method: _CaptureMethod) -> None: self._method: Final = method - self._global_capturing: Optional[MultiCapture[str]] = None - self._capture_fixture: Optional[CaptureFixture[Any]] = None + self._global_capturing: MultiCapture[str] | None = None + self._capture_fixture: CaptureFixture[Any] | None = None def __repr__(self) -> str: - return "".format( - self._method, self._global_capturing, self._capture_fixture + return ( + f"" ) - def is_capturing(self) -> Union[str, bool]: + def is_capturing(self) -> str | bool: if self.is_globally_capturing(): return "global" if self._capture_fixture: - return "fixture %s" % self._capture_fixture.request.fixturename + return f"fixture {self._capture_fixture.request.fixturename}" return False # Global capturing control @@ -781,14 +804,12 @@ class CaptureManager: # Fixture Control - def set_fixture(self, capture_fixture: "CaptureFixture[Any]") -> None: + 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( - "cannot use {} and {} at the same time".format( - requested_fixture, current_fixture - ) + f"cannot use {requested_fixture} and {current_fixture} at the same time" ) self._capture_fixture = capture_fixture @@ -817,7 +838,7 @@ class CaptureManager: # Helper context managers @contextlib.contextmanager - def global_and_fixture_disabled(self) -> Generator[None, None, None]: + 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: @@ -834,7 +855,7 @@ class CaptureManager: self.resume_fixture() @contextlib.contextmanager - def item_capture(self, when: str, item: Item) -> Generator[None, None, None]: + def item_capture(self, when: str, item: Item) -> Generator[None]: self.resume_global_capture() self.activate_fixture() try: @@ -843,41 +864,45 @@ class CaptureManager: 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) + out, err = self.read_global_capture() + item.add_report_section(when, "stdout", out) + item.add_report_section(when, "stderr", err) # Hooks - @hookimpl(hookwrapper=True) - def pytest_make_collect_report(self, collector: Collector): + @hookimpl(wrapper=True) + def pytest_make_collect_report( + self, collector: Collector + ) -> Generator[None, CollectReport, CollectReport]: if isinstance(collector, File): self.resume_global_capture() - outcome = yield - self.suspend_global_capture() + try: + rep = yield + finally: + self.suspend_global_capture() out, err = self.read_global_capture() - rep = outcome.get_result() if out: rep.sections.append(("Captured stdout", out)) if err: rep.sections.append(("Captured stderr", err)) else: - yield + rep = yield + return rep - @hookimpl(hookwrapper=True) - def pytest_runtest_setup(self, item: Item) -> Generator[None, None, None]: + @hookimpl(wrapper=True) + def pytest_runtest_setup(self, item: Item) -> Generator[None]: with self.item_capture("setup", item): - yield + return (yield) - @hookimpl(hookwrapper=True) - def pytest_runtest_call(self, item: Item) -> Generator[None, None, None]: + @hookimpl(wrapper=True) + def pytest_runtest_call(self, item: Item) -> Generator[None]: with self.item_capture("call", item): - yield + return (yield) - @hookimpl(hookwrapper=True) - def pytest_runtest_teardown(self, item: Item) -> Generator[None, None, None]: + @hookimpl(wrapper=True) + def pytest_runtest_teardown(self, item: Item) -> Generator[None]: with self.item_capture("teardown", item): - yield + return (yield) @hookimpl(tryfirst=True) def pytest_keyboard_interrupt(self) -> None: @@ -894,15 +919,17 @@ class CaptureFixture(Generic[AnyStr]): def __init__( self, - captureclass: Type[CaptureBase[AnyStr]], + captureclass: type[CaptureBase[AnyStr]], request: SubRequest, *, + config: dict[str, Any] | None = None, _ispytest: bool = False, ) -> None: check_ispytest(_ispytest) - self.captureclass: Type[CaptureBase[AnyStr]] = captureclass + self.captureclass: type[CaptureBase[AnyStr]] = captureclass self.request = request - self._capture: Optional[MultiCapture[AnyStr]] = None + self._config = config if config else {} + self._capture: MultiCapture[AnyStr] | None = None self._captured_out: AnyStr = self.captureclass.EMPTY_BUFFER self._captured_err: AnyStr = self.captureclass.EMPTY_BUFFER @@ -910,8 +937,8 @@ class CaptureFixture(Generic[AnyStr]): if self._capture is None: self._capture = MultiCapture( in_=None, - out=self.captureclass(1), - err=self.captureclass(2), + out=self.captureclass(1, **self._config), + err=self.captureclass(2, **self._config), ) self._capture.start_capturing() @@ -957,7 +984,7 @@ class CaptureFixture(Generic[AnyStr]): return False @contextlib.contextmanager - def disabled(self) -> Generator[None, None, None]: + def disabled(self) -> Generator[None]: """Temporarily disable capturing while inside the ``with`` block.""" capmanager: CaptureManager = self.request.config.pluginmanager.getplugin( "capturemanager" @@ -970,7 +997,7 @@ class CaptureFixture(Generic[AnyStr]): @fixture -def capsys(request: SubRequest) -> Generator[CaptureFixture[str], None, None]: +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 @@ -998,7 +1025,42 @@ def capsys(request: SubRequest) -> Generator[CaptureFixture[str], None, None]: @fixture -def capsysbinary(request: SubRequest) -> Generator[CaptureFixture[bytes], None, None]: +def capteesys(request: SubRequest) -> Generator[CaptureFixture[str]]: + r"""Enable simultaneous text capturing and pass-through of writes + to ``sys.stdout`` and ``sys.stderr`` as defined by ``--capture=``. + + + The captured output is made available via ``capteesys.readouterr()`` method + calls, which return a ``(out, err)`` namedtuple. + ``out`` and ``err`` will be ``text`` objects. + + The output is also passed-through, allowing it to be "live-printed", + reported, or both as defined by ``--capture=``. + + Returns an instance of :class:`CaptureFixture[str] `. + + Example: + + .. code-block:: python + + def test_output(capteesys): + print("hello") + captured = capteesys.readouterr() + assert captured.out == "hello\n" + """ + capman: CaptureManager = request.config.pluginmanager.getplugin("capturemanager") + capture_fixture = CaptureFixture( + SysCapture, request, config=dict(tee=True), _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()`` @@ -1026,7 +1088,7 @@ def capsysbinary(request: SubRequest) -> Generator[CaptureFixture[bytes], None, @fixture -def capfd(request: SubRequest) -> Generator[CaptureFixture[str], None, None]: +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 @@ -1054,7 +1116,7 @@ def capfd(request: SubRequest) -> Generator[CaptureFixture[str], None, None]: @fixture -def capfdbinary(request: SubRequest) -> Generator[CaptureFixture[bytes], None, None]: +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 diff --git a/venv/lib/python3.10/site-packages/_pytest/compat.py b/venv/lib/python3.10/site-packages/_pytest/compat.py index 1d0add7..72c3d09 100644 --- a/venv/lib/python3.10/site-packages/_pytest/compat.py +++ b/venv/lib/python3.10/site-packages/_pytest/compat.py @@ -1,41 +1,28 @@ -"""Python version compatibility code.""" +# mypy: allow-untyped-defs +"""Python version compatibility code and random general utilities.""" + from __future__ import annotations -import dataclasses +from collections.abc import Callable import enum import functools import inspect -import os -import sys from inspect import Parameter -from inspect import signature +from inspect import Signature +import os from pathlib import Path +import sys from typing import Any -from typing import Callable -from typing import Generic +from typing import Final from typing import NoReturn -from typing import TYPE_CHECKING -from typing import TypeVar import py -# fmt: off -# Workaround for https://github.com/sphinx-doc/sphinx/issues/10351. -# If `overload` is imported from `compat` instead of from `typing`, -# Sphinx doesn't recognize it as `overload` and the API docs for -# overloaded functions look good again. But type checkers handle -# it fine. -# fmt: on -if True: - from typing import overload as overload -if TYPE_CHECKING: - from typing_extensions import Final +if sys.version_info >= (3, 14): + from annotationlib import Format -_T = TypeVar("_T") -_S = TypeVar("_S") - #: constant to prepare valuing pylib path replacements/lazy proxies later on # intended for removal in pytest 8.0 or 9.0 @@ -55,32 +42,16 @@ def legacy_path(path: str | os.PathLike[str]) -> LEGACY_PATH: # https://www.python.org/dev/peps/pep-0484/#support-for-singleton-types-in-unions class NotSetType(enum.Enum): token = 0 -NOTSET: Final = NotSetType.token # noqa: E305 +NOTSET: Final = NotSetType.token # fmt: on -if sys.version_info >= (3, 8): - import importlib.metadata - - importlib_metadata = importlib.metadata -else: - import importlib_metadata as importlib_metadata # noqa: F401 - - -def _format_args(func: Callable[..., Any]) -> str: - return str(signature(func)) - - -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 couroutines.py to avoid + 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). """ @@ -93,7 +64,14 @@ def is_async_function(func: object) -> bool: return iscoroutinefunction(func) or inspect.isasyncgenfunction(func) -def getlocation(function, curdir: str | None = None) -> str: +def signature(obj: Callable[..., Any]) -> Signature: + """Return signature without evaluating annotations.""" + if sys.version_info >= (3, 14): + return inspect.signature(obj, annotation_format=Format.STRING) + return inspect.signature(obj) + + +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 @@ -103,8 +81,8 @@ def getlocation(function, curdir: str | None = None) -> str: except ValueError: pass else: - return "%s:%d" % (relfn, lineno + 1) - return "%s:%d" % (fn, lineno + 1) + return f"{relfn}:{lineno + 1}" + return f"{fn}:{lineno + 1}" def num_mock_patch_args(function) -> int: @@ -127,10 +105,9 @@ def num_mock_patch_args(function) -> int: def getfuncargnames( - function: Callable[..., Any], + function: Callable[..., object], *, name: str = "", - is_method: bool = False, cls: type | None = None, ) -> tuple[str, ...]: """Return the names of a function's mandatory arguments. @@ -141,9 +118,8 @@ def getfuncargnames( * Aren't bound with functools.partial. * Aren't replaced with mocks. - The is_method and cls arguments indicate that the function should - be treated as a bound method even though it's not unless, only in - the case of cls, the function is a static method. + 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. """ @@ -157,7 +133,7 @@ def getfuncargnames( # creates a tuple of the names of the parameters that don't have # defaults. try: - parameters = signature(function).parameters + parameters = signature(function).parameters.values() except (ValueError, TypeError) as e: from _pytest.outcomes import fail @@ -168,7 +144,7 @@ def getfuncargnames( arg_names = tuple( p.name - for p in parameters.values() + for p in parameters if ( p.kind is Parameter.POSITIONAL_OR_KEYWORD or p.kind is Parameter.KEYWORD_ONLY @@ -179,9 +155,9 @@ def getfuncargnames( 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 is_method or ( + # it's passed as an unbound method or function, and its first parameter + # wasn't defined as positional only, remove the first parameter name. + if not any(p.kind is Parameter.POSITIONAL_ONLY for p in parameters) and ( # 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 @@ -216,25 +192,13 @@ _non_printable_ascii_translate_table.update( ) -def _translate_non_printable(s: str) -> str: - return s.translate(_non_printable_ascii_translate_table) - - -STRING_TYPES = bytes, str - - -def _bytes_to_ascii(val: bytes) -> str: - return val.decode("ascii", "backslashreplace") - - 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 unicode objects into a sequence of escaped unicode - ids, e.g.: + and escapes strings into a sequence of escaped unicode ids, e.g.: r'4\nV\U00043efa\x0eMXWB\x1e\u3028\u15fd\xcd\U0007d944' @@ -245,67 +209,22 @@ def ascii_escaped(val: bytes | str) -> str: a UTF-8 string. """ if isinstance(val, bytes): - ret = _bytes_to_ascii(val) + ret = val.decode("ascii", "backslashreplace") else: ret = val.encode("unicode_escape").decode("ascii") - return _translate_non_printable(ret) - - -@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 + return ret.translate(_non_printable_ascii_translate_table) 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 + :func:`functools.wraps`, or :func:`functools.partial`.""" + obj = inspect.unwrap(obj) - raise ValueError( - ("could not find real function of {start}\nstopped at {current}").format( - start=saferepr(start_obj), current=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__ @@ -338,47 +257,6 @@ def safe_isclass(obj: object) -> bool: return False -if TYPE_CHECKING: - if sys.version_info >= (3, 8): - from typing import final as final - else: - from typing_extensions import final as final -elif sys.version_info >= (3, 8): - from typing import final as final -else: - - def final(f): - return f - - -if sys.version_info >= (3, 8): - from functools import cached_property as cached_property -else: - - class cached_property(Generic[_S, _T]): - __slots__ = ("func", "__doc__") - - def __init__(self, func: Callable[[_S], _T]) -> None: - self.func = func - self.__doc__ = func.__doc__ - - @overload - def __get__( - self, instance: None, owner: type[_S] | None = ... - ) -> cached_property[_S, _T]: - ... - - @overload - def __get__(self, instance: _S, owner: type[_S] | None = ...) -> _T: - ... - - def __get__(self, instance, owner=None): - if instance is None: - return self - value = instance.__dict__[self.func.__name__] = self.func(instance) - return value - - def get_user_id() -> int | None: """Return the current process's real user id or None if it could not be determined. @@ -400,36 +278,37 @@ def get_user_id() -> int | None: 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__})" +if sys.version_info >= (3, 11): + from typing import assert_never +else: + + def assert_never(value: NoReturn) -> NoReturn: + assert False, f"Unhandled value: {value} ({type(value).__name__})" + + +class CallableBool: + """ + A bool-like object that can also be called, returning its true/false value. + + Used for backwards compatibility in cases where something was supposed to be a method + but was implemented as a simple attribute by mistake (see `TerminalReporter.isatty`). + + Do not use in new code. + """ + + def __init__(self, value: bool) -> None: + self._value = value + + def __bool__(self) -> bool: + return self._value + + def __call__(self) -> bool: + return self._value + + +def running_on_ci() -> bool: + """Check if we're currently running on a CI system.""" + # Only enable CI mode if one of these env variables is defined and non-empty. + # Note: review `regendoc` tox env in case this list is changed. + env_vars = ["CI", "BUILD_NUMBER"] + return any(os.environ.get(var) for var in env_vars) diff --git a/venv/lib/python3.10/site-packages/_pytest/config/__init__.py b/venv/lib/python3.10/site-packages/_pytest/config/__init__.py index e3990d1..6b02e16 100644 --- a/venv/lib/python3.10/site-packages/_pytest/config/__init__.py +++ b/venv/lib/python3.10/site-packages/_pytest/config/__init__.py @@ -1,55 +1,67 @@ -"""Command line options, ini-file and conftest.py processing.""" +# mypy: allow-untyped-defs +"""Command line options, config-file and conftest.py processing.""" + +from __future__ import annotations + import argparse +import builtins import collections.abc +from collections.abc import Callable +from collections.abc import Generator +from collections.abc import Iterable +from collections.abc import Iterator +from collections.abc import Mapping +from collections.abc import MutableMapping +from collections.abc import Sequence +import contextlib 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 -import types -import warnings -from functools import lru_cache -from pathlib import Path from textwrap import dedent +import types from types import FunctionType -from types import TracebackType from typing import Any -from typing import Callable from typing import cast -from typing import Dict -from typing import Generator +from typing import Final +from typing import final from typing import IO -from typing import Iterable -from typing import Iterator -from typing import List -from typing import Optional -from typing import Sequence -from typing import Set from typing import TextIO -from typing import Tuple -from typing import Type from typing import TYPE_CHECKING -from typing import Union +import warnings +import pluggy from pluggy import HookimplMarker +from pluggy import HookimplOpts from pluggy import HookspecMarker +from pluggy import HookspecOpts from pluggy import PluginManager -import _pytest._code -import _pytest.deprecated -import _pytest.hookspec +from .compat import PathAwareHookProxy from .exceptions import PrintHelp as PrintHelp from .exceptions import UsageError as UsageError +from .findpaths import ConfigValue 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.compat import final -from _pytest.compat import importlib_metadata # type: ignore[attr-defined] +from _pytest.compat import assert_never +from _pytest.config.argparsing import Argument +from _pytest.config.argparsing import FILE_OR_DIR +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 @@ -62,11 +74,11 @@ from _pytest.stash import Stash from _pytest.warning_types import PytestConfigWarning from _pytest.warning_types import warn_explicit_for -if TYPE_CHECKING: - from _pytest._code.code import _TracebackStyle - from _pytest.terminal import TerminalReporter - from .argparsing import Argument +if TYPE_CHECKING: + from _pytest.assertion.rewrite import AssertionRewritingHook + from _pytest.cacheprovider import Cache + from _pytest.terminal import TerminalReporter _PluggyPlugin = object """A type to represent plugin objects. @@ -104,21 +116,21 @@ class ExitCode(enum.IntEnum): #: pytest couldn't find tests. NO_TESTS_COLLECTED = 5 + __module__ = "pytest" + class ConftestImportFailure(Exception): def __init__( self, - path: Path, - excinfo: Tuple[Type[Exception], Exception, TracebackType], + path: pathlib.Path, + *, + cause: Exception, ) -> None: - super().__init__(path, excinfo) self.path = path - self.excinfo = excinfo + self.cause = cause def __str__(self) -> str: - return "{}: {} (from {})".format( - self.excinfo[0].__name__, self.excinfo[1], self.path - ) + return f"{type(self.cause).__name__}: {self.cause} (from {self.path})" def filter_traceback_for_conftest_import_failure( @@ -132,10 +144,33 @@ def filter_traceback_for_conftest_import_failure( return filter_traceback(entry) and "importlib" not in str(entry.path).split(os.sep) +def print_conftest_import_error(e: ConftestImportFailure, file: TextIO) -> None: + exc_info = ExceptionInfo.from_exception(e.cause) + tw = TerminalWriter(file) + 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) + + +def print_usage_error(e: UsageError, file: TextIO) -> None: + tw = TerminalWriter(file) + for msg in e.args: + tw.line(f"ERROR: {msg}\n", red=True) + + def main( - args: Optional[Union[List[str], "os.PathLike[str]"]] = None, - plugins: Optional[Sequence[Union[str, _PluggyPlugin]]] = None, -) -> Union[int, ExitCode]: + args: list[str] | os.PathLike[str] | None = None, + plugins: Sequence[str | _PluggyPlugin] | None = None, +) -> int | ExitCode: """Perform an in-process test run. :param args: @@ -145,41 +180,37 @@ def main( :returns: An exit code. """ + # Handle a single `--version` argument early to avoid starting up the entire pytest infrastructure. + new_args = sys.argv[1:] if args is None else args + if isinstance(new_args, Sequence) and new_args.count("--version") == 1: + sys.stdout.write(f"pytest {__version__}\n") + return ExitCode.OK + + old_pytest_version = os.environ.get("PYTEST_VERSION") try: + os.environ["PYTEST_VERSION"] = __version__ try: - config = _prepareconfig(args, plugins) + config = _prepareconfig(new_args, plugins) except ConftestImportFailure as e: - exc_info = ExceptionInfo.from_exc_info(e.excinfo) - 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) + print_conftest_import_error(e, file=sys.stderr) return ExitCode.USAGE_ERROR - else: + + try: + ret: ExitCode | int = config.hook.pytest_cmdline_main(config=config) try: - ret: Union[ExitCode, int] = config.hook.pytest_cmdline_main( - config=config - ) - try: - return ExitCode(ret) - except ValueError: - return ret - finally: - config._ensure_unconfigure() + 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) + print_usage_error(e, file=sys.stderr) 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: @@ -235,7 +266,8 @@ essential_plugins = ( "helpconfig", # Provides -p. ) -default_plugins = essential_plugins + ( +default_plugins = ( + *essential_plugins, "python", "terminal", "debugging", @@ -247,46 +279,46 @@ default_plugins = essential_plugins + ( "monkeypatch", "recwarn", "pastebin", - "nose", "assertion", "junitxml", "doctest", "cacheprovider", - "freeze_support", "setuponly", "setupplan", "stepwise", + "unraisableexception", + "threadexception", "warnings", "logging", "reports", - "python_path", - *(["unraisableexception", "threadexception"] if sys.version_info >= (3, 8) else []), "faulthandler", + "subtests", ) -builtin_plugins = set(default_plugins) -builtin_plugins.add("pytester") -builtin_plugins.add("pytester_assertions") +builtin_plugins = { + *default_plugins, + "pytester", + "pytester_assertions", + "terminalprogress", +} def get_config( - args: Optional[List[str]] = None, - plugins: Optional[Sequence[Union[str, _PluggyPlugin]]] = None, -) -> "Config": - # subsequent calls to main will create a fresh instance + args: Iterable[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=Path.cwd(), - ), + invocation_params = Config.InvocationParams( + args=args or (), + plugins=plugins, + dir=pathlib.Path.cwd(), ) + config = Config(pluginmanager, invocation_params=invocation_params) - if args is not None: + if invocation_params.args: # Handle any "-p no:plugin" args. - pluginmanager.consider_preparse(args, exclude_only=True) + pluginmanager.consider_preparse(invocation_params.args, exclude_only=True) for spec in default_plugins: pluginmanager.import_plugin(spec) @@ -294,7 +326,7 @@ def get_config( return config -def get_plugin_manager() -> "PytestPluginManager": +def get_plugin_manager() -> PytestPluginManager: """Obtain a new instance of the :py:class:`pytest.PytestPluginManager`, with default plugins already loaded. @@ -306,12 +338,10 @@ def get_plugin_manager() -> "PytestPluginManager": def _prepareconfig( - args: Optional[Union[List[str], "os.PathLike[str]"]] = None, - plugins: Optional[Sequence[Union[str, _PluggyPlugin]]] = None, -) -> "Config": - if args is None: - args = sys.argv[1:] - elif isinstance(args, os.PathLike): + args: list[str] | os.PathLike[str], + plugins: Sequence[str | _PluggyPlugin] | None = None, +) -> Config: + if isinstance(args, os.PathLike): args = [os.fspath(args)] elif not isinstance(args, list): msg = ( # type:ignore[unreachable] @@ -319,8 +349,8 @@ def _prepareconfig( ) raise TypeError(msg.format(args, type(args))) - config = get_config(args, plugins) - pluginmanager = config.pluginmanager + initial_config = get_config(args, plugins) + pluginmanager = initial_config.pluginmanager try: if plugins: for plugin in plugins: @@ -328,16 +358,16 @@ def _prepareconfig( pluginmanager.consider_pluginarg(plugin) else: pluginmanager.register(plugin) - config = pluginmanager.hook.pytest_cmdline_parse( + config: Config = pluginmanager.hook.pytest_cmdline_parse( pluginmanager=pluginmanager, args=args ) return config except BaseException: - config._ensure_unconfigure() + initial_config._ensure_unconfigure() raise -def _get_directory(path: Path) -> Path: +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 @@ -348,10 +378,10 @@ def _get_directory(path: Path) -> Path: def _get_legacy_hook_marks( method: Any, hook_type: str, - opt_names: Tuple[str, ...], -) -> Dict[str, bool]: + opt_names: tuple[str, ...], +) -> dict[str, bool]: if TYPE_CHECKING: - # abuse typeguard from importlib to avoid massive method type union thats lacking a alias + # 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] = [] @@ -388,19 +418,20 @@ class PytestPluginManager(PluginManager): """ def __init__(self) -> None: - import _pytest.assertion + from _pytest.assertion import DummyRewriteHook + from _pytest.assertion import RewriteHook super().__init__("pytest") # -- State related to local conftest plugins. # All loaded conftest modules. - self._conftest_plugins: Set[types.ModuleType] = set() + 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[Path, List[types.ModuleType]] = {} + self._dirpath2confmods: dict[pathlib.Path, list[types.ModuleType]] = {} # Cutoff directory above which conftests are no longer discovered. - self._confcutdir: Optional[Path] = None + self._confcutdir: pathlib.Path | None = None # If set, conftest loading is skipped. self._noconftest = False @@ -409,14 +440,12 @@ class PytestPluginManager(PluginManager): # session (#9478), often with the same path, so cache it. self._get_directory = lru_cache(256)(_get_directory) - self._duplicatepaths: Set[Path] = set() - # 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.skipped_plugins: list[tuple[str, str]] = [] self.add_hookspecs(_pytest.hookspec) self.register(self) @@ -436,17 +465,20 @@ class PytestPluginManager(PluginManager): self.enable_tracing() # Config._consider_importhook will set a real object if required. - self.rewrite_hook = _pytest.assertion.DummyRewriteHook() + self.rewrite_hook: RewriteHook = 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): + 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 can not be hooks. + # Ignore names which cannot be hooks. if name == "pytest_plugins": return None @@ -459,25 +491,24 @@ class PytestPluginManager(PluginManager): 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] + legacy = _get_legacy_hook_marks( method, "impl", ("tryfirst", "trylast", "optionalhook", "hookwrapper") ) + return cast(HookimplOpts, legacy) - def parse_hookspec_opts(self, module_or_class, name: str): + 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"), + legacy = _get_legacy_hook_marks( + method, "spec", ("firstresult", "historic") ) + opts = cast(HookspecOpts, legacy) return opts - def register( - self, plugin: _PluggyPlugin, name: Optional[str] = None - ) -> Optional[str]: + def register(self, plugin: _PluggyPlugin, name: str | None = None) -> str | None: if name in _pytest.deprecated.DEPRECATED_EXTERNAL_PLUGINS: warnings.warn( PytestConfigWarning( @@ -488,26 +519,30 @@ class PytestPluginManager(PluginManager): ) ) return None - ret: Optional[str] = super().register(plugin, name) - if ret: + plugin_name = super().register(plugin, name) + if plugin_name is not None: self.hook.pytest_plugin_registered.call_historic( - kwargs=dict(plugin=plugin, manager=self) + kwargs=dict( + plugin=plugin, + plugin_name=plugin_name, + manager=self, + ) ) if isinstance(plugin, types.ModuleType): self.consider_module(plugin) - return ret + return plugin_name def getplugin(self, name: str): # Support deprecated naming because plugins (xdist e.g.) use it. - plugin: Optional[_PluggyPlugin] = self.get_plugin(name) + 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: + def pytest_configure(self, config: Config) -> None: """:meta private:""" # XXX now that the pluginmanager exposes hookimpl(tryfirst...) # we should remove tryfirst/trylast as markers. @@ -530,12 +565,15 @@ class PytestPluginManager(PluginManager): # def _set_initial_conftests( self, - args: Sequence[Union[str, Path]], + args: Sequence[str | pathlib.Path], pyargs: bool, noconftest: bool, - rootpath: Path, - confcutdir: Optional[Path], - importmode: Union[ImportMode, str], + 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". @@ -544,81 +582,120 @@ class PytestPluginManager(PluginManager): All builtin and 3rd party plugins will have been loaded, however, so common options will not confuse our logic here. """ - current = Path.cwd() - self._confcutdir = absolutepath(current / confcutdir) if confcutdir else None + self._confcutdir = ( + absolutepath(invocation_dir / confcutdir) if confcutdir else None + ) self._noconftest = noconftest self._using_pyargs = pyargs foundanchor = False - for intitial_path in args: - path = str(intitial_path) + for initial_path in args: + path = str(initial_path) # remove node-id syntax i = path.find("::") if i != -1: path = path[:i] - anchor = absolutepath(current / path) + 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) + self._try_load_conftest( + anchor, + importmode, + rootpath, + consider_namespace_packages=consider_namespace_packages, + ) foundanchor = True if not foundanchor: - self._try_load_conftest(current, importmode, rootpath) + self._try_load_conftest( + invocation_dir, + importmode, + rootpath, + consider_namespace_packages=consider_namespace_packages, + ) - def _is_in_confcutdir(self, path: Path) -> bool: - """Whether a path is within the confcutdir. - - When false, should not load conftest. - """ + 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: Path, importmode: Union[str, ImportMode], rootpath: Path + self, + anchor: pathlib.Path, + importmode: str | ImportMode, + rootpath: pathlib.Path, + *, + consider_namespace_packages: bool, ) -> None: - self._getconftestmodules(anchor, importmode, rootpath) + 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._getconftestmodules(x, importmode, rootpath) + self._loadconftestmodules( + x, + importmode, + rootpath, + consider_namespace_packages=consider_namespace_packages, + ) - def _getconftestmodules( - self, path: Path, importmode: Union[str, ImportMode], rootpath: Path - ) -> Sequence[types.ModuleType]: + def _loadconftestmodules( + self, + path: pathlib.Path, + importmode: str | ImportMode, + rootpath: pathlib.Path, + *, + consider_namespace_packages: bool, + ) -> None: if self._noconftest: - return [] + return directory = self._get_directory(path) # Optimization: avoid repeated searches in the same directory. # Assumes always called with same importmode and rootpath. - existing_clist = self._dirpath2confmods.get(directory) - if existing_clist is not None: - return existing_clist + if directory in self._dirpath2confmods: + return - # XXX these days we may rather want to use config.rootpath - # and allow users to opt into looking into the rootdir parent - # directories instead of requiring to specify confcutdir. 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) + mod = self._importconftest( + conftestpath, + importmode, + rootpath, + consider_namespace_packages=consider_namespace_packages, + ) clist.append(mod) self._dirpath2confmods[directory] = clist - return 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: Path, - importmode: Union[str, ImportMode], - rootpath: Path, - ) -> Tuple[types.ModuleType, Any]: - modules = self._getconftestmodules(path, importmode, rootpath=rootpath) + path: pathlib.Path, + ) -> tuple[types.ModuleType, Any]: + modules = self._getconftestmodules(path) for mod in reversed(modules): try: return mod, getattr(mod, name) @@ -627,22 +704,39 @@ class PytestPluginManager(PluginManager): raise KeyError(name) def _importconftest( - self, conftestpath: Path, importmode: Union[str, ImportMode], rootpath: Path + self, + conftestpath: pathlib.Path, + importmode: str | ImportMode, + rootpath: pathlib.Path, + *, + consider_namespace_packages: bool, ) -> types.ModuleType: - existing = self.get_plugin(str(conftestpath)) + 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: - _ensure_removed_sysmodule(conftestpath.stem) + try: + del sys.modules[conftestpath.stem] + except KeyError: + pass try: - mod = import_path(conftestpath, mode=importmode, root=rootpath) + mod = import_path( + conftestpath, + mode=importmode, + root=rootpath, + consider_namespace_packages=consider_namespace_packages, + ) except Exception as e: assert e.__traceback__ is not None - exc_info = (type(e), e, e.__traceback__) - raise ConftestImportFailure(conftestpath, exc_info) from e + raise ConftestImportFailure(conftestpath, cause=e) from e self._check_non_top_pytest_plugins(mod, conftestpath) @@ -651,16 +745,21 @@ class PytestPluginManager(PluginManager): if dirpath in self._dirpath2confmods: for path, mods in self._dirpath2confmods.items(): if dirpath in path.parents or path == dirpath: - assert mod not in mods + 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) + self.consider_conftest(mod, registration_name=conftestpath_plugin_name) return mod def _check_non_top_pytest_plugins( self, mod: types.ModuleType, - conftestpath: Path, + conftestpath: pathlib.Path, ) -> None: if ( hasattr(mod, "pytest_plugins") @@ -713,7 +812,7 @@ class PytestPluginManager(PluginManager): if arg.startswith("no:"): name = arg[3:] if name in essential_plugins: - raise UsageError("plugin %s cannot be disabled" % name) + raise UsageError(f"plugin {name} cannot be disabled") # PR #4304: remove stepwise if cacheprovider is blocked. if name == "cacheprovider": @@ -725,18 +824,17 @@ class PytestPluginManager(PluginManager): self.set_blocked("pytest_" + name) else: name = arg - # Unblock the plugin. None indicates that it has been blocked. - # There is no interface with pluggy for this. - if self._name2plugin.get(name, -1) is None: - del self._name2plugin[name] + # Unblock the plugin. + self.unblock(name) if not name.startswith("pytest_"): - if self._name2plugin.get("pytest_" + name, -1) is None: - del self._name2plugin["pytest_" + name] + self.unblock("pytest_" + name) self.import_plugin(arg, consider_entry_points=True) - def consider_conftest(self, conftestmodule: types.ModuleType) -> None: + def consider_conftest( + self, conftestmodule: types.ModuleType, registration_name: str + ) -> None: """:meta private:""" - self.register(conftestmodule, name=conftestmodule.__file__) + self.register(conftestmodule, name=registration_name) def consider_env(self) -> None: """:meta private:""" @@ -747,7 +845,7 @@ class PytestPluginManager(PluginManager): self._import_plugin_specs(getattr(mod, "pytest_plugins", [])) def _import_plugin_specs( - self, spec: Union[None, types.ModuleType, str, Sequence[str]] + self, spec: None | types.ModuleType | str | Sequence[str] ) -> None: plugins = _get_plugin_specs_as_list(spec) for import_spec in plugins: @@ -764,7 +862,7 @@ class PytestPluginManager(PluginManager): # basename for historic purposes but must be imported with the # _pytest prefix. assert isinstance(modname, str), ( - "module name as text required, got %r" % modname + f"module name as text required, got {modname!r}" ) if self.is_blocked(modname) or self.get_plugin(modname) is not None: return @@ -792,8 +890,8 @@ class PytestPluginManager(PluginManager): def _get_plugin_specs_as_list( - specs: Union[None, types.ModuleType, str, Sequence[str]] -) -> List[str]: + 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: @@ -808,18 +906,10 @@ def _get_plugin_specs_as_list( if isinstance(specs, collections.abc.Sequence): return list(specs) raise UsageError( - "Plugins may be specified as a sequence or a ','-separated string of plugin names. Got: %r" - % specs + f"Plugins may be specified as a sequence or a ','-separated string of plugin names. Got: {specs!r}" ) -def _ensure_removed_sysmodule(modname: str) -> None: - try: - del sys.modules[modname] - except KeyError: - pass - - class Notset: def __repr__(self): return "" @@ -893,6 +983,30 @@ def _iter_rewritable_modules(package_files: Iterable[str]) -> Iterator[str]: yield from _iter_rewritable_modules(new_package_files) +class _DeprecatedInicfgProxy(MutableMapping[str, Any]): + """Compatibility proxy for the deprecated Config.inicfg.""" + + __slots__ = ("_config",) + + def __init__(self, config: Config) -> None: + self._config = config + + def __getitem__(self, key: str) -> Any: + return self._config._inicfg[key].value + + def __setitem__(self, key: str, value: Any) -> None: + self._config._inicfg[key] = ConfigValue(value, origin="override", mode="toml") + + def __delitem__(self, key: str) -> None: + del self._config._inicfg[key] + + def __iter__(self) -> Iterator[str]: + return iter(self._config._inicfg) + + def __len__(self) -> int: + return len(self._config._inicfg) + + @final class Config: """Access to configuration values, pluginmanager and plugin hooks. @@ -917,24 +1031,24 @@ class Config: .. note:: Note that the environment variable ``PYTEST_ADDOPTS`` and the ``addopts`` - ini option are handled by pytest, not being included in the ``args`` attribute. + configuration option are handled by pytest, not being included in the ``args`` attribute. Plugins accessing ``InvocationParams`` must be aware of that. """ - args: Tuple[str, ...] + args: tuple[str, ...] """The command-line arguments as passed to :func:`pytest.main`.""" - plugins: Optional[Sequence[Union[str, _PluggyPlugin]]] + plugins: Sequence[str | _PluggyPlugin] | None """Extra plugins, might be `None`.""" - dir: Path + dir: pathlib.Path """The directory from which :func:`pytest.main` was invoked.""" def __init__( self, *, args: Iterable[str], - plugins: Optional[Sequence[Union[str, _PluggyPlugin]]], - dir: Path, + plugins: Sequence[str | _PluggyPlugin] | None, + dir: pathlib.Path, ) -> None: object.__setattr__(self, "args", tuple(args)) object.__setattr__(self, "plugins", plugins) @@ -949,21 +1063,23 @@ class Config: #: Command line arguments. ARGS = enum.auto() #: Invocation directory. - INCOVATION_DIR = enum.auto() + 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: Optional[InvocationParams] = None, + invocation_params: InvocationParams | None = None, ) -> None: - from .argparsing import Parser, FILE_OR_DIR - if invocation_params is None: invocation_params = self.InvocationParams( - args=(), plugins=None, dir=Path.cwd() + args=(), plugins=None, dir=pathlib.Path.cwd() ) self.option = argparse.Namespace() @@ -978,9 +1094,8 @@ class Config: :type: InvocationParams """ - _a = FILE_OR_DIR self._parser = Parser( - usage=f"%(prog)s [options] [{_a}] [{_a}] [...]", + usage=f"%(prog)s [options] [{FILE_OR_DIR}] [{FILE_OR_DIR}] [...]", processopt=self._processopt, _ispytest=True, ) @@ -999,92 +1114,90 @@ class Config: # Deprecated alias. Was never public. Can be removed in a few releases. self._store = self.stash - from .compat import PathAwareHookProxy - self.trace = self.pluginmanager.trace.root.get("config") - self.hook = PathAwareHookProxy(self.pluginmanager.hook) - self._inicache: Dict[str, Any] = {} - self._override_ini: Sequence[str] = () - self._opt2dest: Dict[str, str] = {} - self._cleanup: List[Callable[[], None]] = [] + self.hook: pluggy.HookRelay = PathAwareHookProxy(self.pluginmanager.hook) # type: ignore[assignment] + self._inicache: dict[str, Any] = {} + self._opt2dest: dict[str, str] = {} + self._cleanup_stack = contextlib.ExitStack() 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] = [] - - if TYPE_CHECKING: - from _pytest.cacheprovider import Cache - - self.cache: Optional[Cache] = None + self.args: list[str] = [] @property - def rootpath(self) -> Path: - """The path to the :ref:`rootdir `. + def inicfg(self) -> _DeprecatedInicfgProxy: + return _DeprecatedInicfgProxy(self) - :type: pathlib.Path + @property + def rootpath(self) -> pathlib.Path: + """The path to the :ref:`rootdir `. .. versionadded:: 6.1 """ return self._rootpath @property - def inipath(self) -> Optional[Path]: + def inipath(self) -> pathlib.Path | None: """The path to the :ref:`configfile `. - :type: Optional[pathlib.Path] - .. 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) + use (usually coinciding with pytest_unconfigure). + """ + self._cleanup_stack.callback(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)) + 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() + try: + if self._configured: + self._configured = False + try: + self.hook.pytest_unconfigure(config=self) + finally: + self.hook.pytest_configure._call_history = [] + finally: + try: + self._cleanup_stack.close() + finally: + self._cleanup_stack = contextlib.ExitStack() def get_terminal_writer(self) -> TerminalWriter: - terminalreporter: Optional[TerminalReporter] = self.pluginmanager.get_plugin( + 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": + self, pluginmanager: PytestPluginManager, args: list[str] + ) -> Config: try: self.parse(args) except UsageError: - # Handle --version and --help here in a minimal fashion. + # Handle `--version --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 + from _pytest.helpconfig import show_version_verbose - showversion(self) + # Note that `--version` (single argument) is handled early by `Config.main()`, so the only + # way we are reaching this point is via `--version --version`. + show_version_verbose(self) elif ( getattr(self.option, "help", False) or "--help" in args or "-h" in args ): - self._parser._getparser().print_help() + self._parser.optparser.print_help() sys.stdout.write( "\nNOTE: displaying only minimal help due to UsageError.\n\n" ) @@ -1096,10 +1209,10 @@ class Config: def notify_exception( self, excinfo: ExceptionInfo[BaseException], - option: Optional[argparse.Namespace] = None, + option: argparse.Namespace | None = None, ) -> None: if option and getattr(option, "fulltrace", False): - style: _TracebackStyle = "long" + style: TracebackStyle = "long" else: style = "native" excrepr = excinfo.getrepr( @@ -1108,18 +1221,22 @@ class Config: res = self.hook.pytest_internalerror(excrepr=excrepr, excinfo=excinfo) if not any(res): for line in str(excrepr).split("\n"): - sys.stderr.write("INTERNALERROR> %s\n" % line) + 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: - fullpath = self.rootpath / nodeid - nodeid = bestrelpath(self.invocation_params.dir, fullpath) + 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": + def fromdictargs(cls, option_dict: Mapping[str, Any], args: list[str]) -> Config: """Constructor usable for subprocesses.""" config = get_config(args) config.option.__dict__.update(option_dict) @@ -1128,7 +1245,7 @@ class Config: config.pluginmanager.consider_pluginarg(x) return config - def _processopt(self, opt: "Argument") -> None: + def _processopt(self, opt: Argument) -> None: for name in opt._short_opts + opt._long_opts: self._opt2dest[name] = opt.dest @@ -1137,12 +1254,12 @@ class Config: setattr(self.option, opt.dest, opt.default) @hookimpl(trylast=True) - def pytest_load_initial_conftests(self, early_config: "Config") -> None: + 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, _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"), @@ -1156,43 +1273,25 @@ class Config: 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( - ns.inifilename, - ns.file_or_dir + unknown_args, - rootdir_cmd_arg=ns.rootdir or None, - config=self, - ) - 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: + def _consider_importhook(self) -> 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") + mode = getattr(self.known_args_namespace, "assertmode", "plain") + + disable_autoload = getattr( + self.known_args_namespace, "disable_plugin_autoload", False + ) or bool(os.environ.get("PYTEST_DISABLE_PLUGIN_AUTOLOAD")) if mode == "rewrite": import _pytest.assertion @@ -1201,22 +1300,25 @@ class Config: except SystemError: mode = "plain" else: - self._mark_plugins_for_rewrite(hook) + self._mark_plugins_for_rewrite(hook, disable_autoload) self._warn_about_missing_assertion(mode) - def _mark_plugins_for_rewrite(self, hook) -> None: + def _mark_plugins_for_rewrite( + self, hook: AssertionRewritingHook, disable_autoload: bool + ) -> 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 setuptools entry points, no need to continue. + if disable_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() + for dist in importlib.metadata.distributions() if any(ep.group == "pytest11" for ep in dist.entry_points) for file in dist.files or [] ) @@ -1224,31 +1326,45 @@ class Config: for name in _iter_rewritable_modules(package_files): hook.mark_rewrite(name) - def _validate_args(self, args: List[str], via: str) -> List[str]: + def _configure_python_path(self) -> None: + # `pythonpath = a b` will set `sys.path` to `[a, b, x, y, z, ...]` + for path in reversed(self.getini("pythonpath")): + sys.path.insert(0, str(path)) + self.add_cleanup(self._unconfigure_python_path) + + def _unconfigure_python_path(self) -> None: + for path in self.getini("pythonpath"): + path_str = str(path) + if path_str in sys.path: + sys.path.remove(path_str) + + def _validate_args(self, args: list[str], via: str) -> list[str]: """Validate known args.""" - self._parser._config_source_hint = via # type: ignore + self._parser.extra_info["config source"] = via try: self._parser.parse_known_and_unknown_args( args, namespace=copy.copy(self.option) ) finally: - del self._parser._config_source_hint # type: ignore + self._parser.extra_info.pop("config source", None) return args def _decide_args( self, *, - args: List[str], - pyargs: List[str], - testpaths: List[str], - invocation_dir: Path, - rootpath: Path, + args: list[str], + pyargs: bool, + testpaths: list[str], + invocation_dir: pathlib.Path, + rootpath: pathlib.Path, warn: bool, - ) -> Tuple[List[str], ArgsSource]: + ) -> 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 @@ -1275,97 +1391,36 @@ class Config: else: result = [] if not result: - source = Config.ArgsSource.INCOVATION_DIR + 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 setuptools 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.strict: - self.issue_config_time_warning( - _pytest.deprecated.STRICT_OPTION, stacklevel=2 - ) - - 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 + @hookimpl(wrapper=True) + def pytest_collection(self) -> Generator[None, object, object]: + # Validate invalid configuration keys after collection is done so we + # take in account options added by late-loading conftest files. 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(hookwrapper=True) - def pytest_collection(self) -> Generator[None, None, None]: - # Validate invalid ini keys after collection is done so we take in account - # options added by late-loading conftest files. - yield - self._validate_config_options() + return (yield) + finally: + self._validate_config_options() def _checkversion(self) -> None: import pytest - minver = self.inicfg.get("minversion", None) + minver_ini_value = self._inicfg.get("minversion", None) + minver = minver_ini_value.value if minver_ini_value is not None else None if minver: # Imported lazily to improve start-up time. from packaging.version import Version if not isinstance(minver, str): raise pytest.UsageError( - "%s: 'minversion' must be a single value" % self.inipath + f"{self.inipath}: 'minversion' must be a single value" ) if Version(minver) > Version(pytest.__version__): raise pytest.UsageError( - "%s: 'minversion' requires pytest-%s, actual pytest-%s'" - % ( - self.inipath, - minver, - pytest.__version__, - ) + f"{self.inipath}: 'minversion' requires pytest-{minver}, actual pytest-{pytest.__version__}'" ) def _validate_config_options(self) -> None: @@ -1378,8 +1433,9 @@ class Config: return # Imported lazily to improve start-up time. + from packaging.requirements import InvalidRequirement + from packaging.requirements import Requirement from packaging.version import Version - from packaging.requirements import InvalidRequirement, Requirement plugin_info = self.pluginmanager.list_plugin_distinfo() plugin_dist_info = {dist.project_name: dist.version for _, dist in plugin_info} @@ -1405,47 +1461,131 @@ class Config: ) def _warn_or_fail_if_strict(self, message: str) -> None: - if self.known_args_namespace.strict_config: + strict_config = self.getini("strict_config") + if strict_config is None: + strict_config = self.getini("strict") + if 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 _get_unknown_ini_keys(self) -> set[str]: + known_keys = self._parser._inidict.keys() | self._parser._ini_aliases.keys() + return self._inicfg.keys() - known_keys - def parse(self, args: List[str], addopts: bool = True) -> None: + 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" + 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) - # XXX deprecated hook: - self.hook.pytest_cmdline_preparse(config=self, args=args) - self._parser.after_preparse = True # type: ignore + + 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 + ) + + ns = self._parser.parse_known_args(args, namespace=copy.copy(self.option)) + rootpath, inipath, inicfg, ignored_config_files = determine_setup( + inifile=ns.inifilename, + override_ini=ns.override_ini, + args=ns.file_or_dir, + rootdir_cmd_arg=ns.rootdir or None, + invocation_dir=self.invocation_params.dir, + ) + self._rootpath = rootpath + self._inipath = inipath + self._ignored_config_files = ignored_config_files + 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( + "pythonpath", type="paths", help="Add paths to sys.path", default=[] + ) + self._parser.addini( + "required_plugins", + "Plugins that must be present for pytest to run", + type="args", + default=[], + ) + + 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() + self._configure_python_path() + self.pluginmanager.consider_preparse(args, exclude_only=False) + if ( + not os.environ.get("PYTEST_DISABLE_PLUGIN_AUTOLOAD") + and not self.known_args_namespace.disable_plugin_autoload + ): + # Autoloading from distribution package entry point has + # not been disabled. + self.pluginmanager.load_setuptools_entrypoints("pytest11") + # Otherwise only plugins explicitly specified in PYTEST_PLUGINS + # are going to be loaded. + self.pluginmanager.consider_env() + + self._parser.parse_known_args(args, namespace=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: - 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, + 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 it 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 + + try: + self._parser.parse(args, namespace=self.option) except PrintHelp: - pass + return + + self.args, self.args_source = self._decide_args( + args=getattr(self.option, FILE_OR_DIR), + pyargs=self.option.pyargs, + testpaths=self.getini("testpaths"), + invocation_dir=self.invocation_params.dir, + rootpath=self.rootpath, + warn=True, + ) 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 hookwrappers around ``pytest_configure``. + 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). @@ -1477,68 +1617,129 @@ class Config: ) def addinivalue_line(self, name: str, line: str) -> None: - """Add a line to an ini-file option. The option must have been + """Add a line to a configuration 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 `. + def getini(self, name: str) -> Any: + """Return configuration value the an :ref:`configuration file `. + + If a configuration value is not defined in a + :ref:`configuration 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 ``""`` + ``int`` : ``0`` + ``float`` : ``0.0`` + + 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. """ + canonical_name = self._parser._ini_aliases.get(name, name) try: - return self._inicache[name] + return self._inicache[canonical_name] except KeyError: - self._inicache[name] = val = self._getini(name) - return val + pass + self._inicache[canonical_name] = val = self._getini(canonical_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: Union[str, List[str]]): - msg = f"unknown configuration type: {type}" - raise ValueError(msg, value) # pragma: no cover + def _getini_unknown_type(self, name: str, type: str, value: object): + msg = ( + f"Option {name} has unknown configuration type {type} with value {value!r}" + ) + raise ValueError(msg) # pragma: no cover def _getini(self, name: str): + # If this is an alias, resolve to canonical name. + canonical_name = self._parser._ini_aliases.get(name, name) + try: - description, type, default = self._parser._inidict[name] + _description, type, default = self._parser._inidict[canonical_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: - if default is not None: - return default - if type is None: - return "" - return [] + + # Collect all possible values (canonical name + aliases) from _inicfg. + # Each candidate is (ConfigValue, is_canonical). + candidates = [] + if canonical_name in self._inicfg: + candidates.append((self._inicfg[canonical_name], True)) + for alias, target in self._parser._ini_aliases.items(): + if target == canonical_name and alias in self._inicfg: + candidates.append((self._inicfg[alias], False)) + + if not candidates: + return default + + # Pick the best candidate based on precedence: + # 1. CLI override takes precedence over file, then + # 2. Canonical name takes precedence over alias. + selected = max(candidates, key=lambda x: (x[0].origin == "override", x[1]))[0] + value = selected.value + mode = selected.mode + + if mode == "ini": + # In ini mode, values are always str | list[str]. + assert isinstance(value, (str, list)) + return self._getini_ini(name, canonical_name, type, value, default) + elif mode == "toml": + return self._getini_toml(name, canonical_name, type, value, 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: + assert_never(mode) + + def _getini_ini( + self, + name: str, + canonical_name: str, + type: str, + value: str | list[str], + default: Any, + ): + """Handle config values read in INI mode. + + In INI mode, values are stored as str or list[str] only, and coerced + from string based on the registered type. + """ + # 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 (in ini mode) we will get either str or list of + # str values (see load_config_dict_from_file). For example: # # ini: # a_line_list = "tests acceptance" - # in this case, we need to split the string to obtain a list of strings. # - # toml: + # in this case, we need to split the string to obtain a list of strings. + # + # toml (ini mode): # a_line_list = ["tests", "acceptance"] - # in this case, we already have a list ready to use. # + # in this case, we already have a list ready to use. if type == "paths": - # TODO: This assert is probably not valid in all cases. - assert self.inipath is not None - dp = self.inipath.parent + 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": @@ -1552,59 +1753,133 @@ class Config: return _strtobool(str(value).strip()) elif type == "string": return value - elif type is None: + elif type == "int": + if not isinstance(value, str): + raise TypeError( + f"Expected an int string for option {name} of type integer, but got: {value!r}" + ) from None + return int(value) + elif type == "float": + if not isinstance(value, str): + raise TypeError( + f"Expected a float string for option {name} of type float, but got: {value!r}" + ) from None + return float(value) + else: + return self._getini_unknown_type(name, type, value) + + def _getini_toml( + self, + name: str, + canonical_name: str, + type: str, + value: object, + default: Any, + ): + """Handle TOML config values with strict type validation and no coercion. + + In TOML mode, values already have native types from TOML parsing. + We validate types match expectations exactly, including list items. + """ + value_type = builtins.type(value).__name__ + if type == "paths": + # Expect a list of strings. + if not isinstance(value, list): + raise TypeError( + f"{self.inipath}: config option '{name}' expects a list for type 'paths', " + f"got {value_type}: {value!r}" + ) + for i, item in enumerate(value): + if not isinstance(item, str): + item_type = builtins.type(item).__name__ + raise TypeError( + f"{self.inipath}: config option '{name}' expects a list of strings, " + f"but item at index {i} is {item_type}: {item!r}" + ) + dp = ( + self.inipath.parent + if self.inipath is not None + else self.invocation_params.dir + ) + return [dp / x for x in value] + elif type in {"args", "linelist"}: + # Expect a list of strings. + if not isinstance(value, list): + raise TypeError( + f"{self.inipath}: config option '{name}' expects a list for type '{type}', " + f"got {value_type}: {value!r}" + ) + for i, item in enumerate(value): + if not isinstance(item, str): + item_type = builtins.type(item).__name__ + raise TypeError( + f"{self.inipath}: config option '{name}' expects a list of strings, " + f"but item at index {i} is {item_type}: {item!r}" + ) + return list(value) + elif type == "bool": + # Expect a boolean. + if not isinstance(value, bool): + raise TypeError( + f"{self.inipath}: config option '{name}' expects a bool, " + f"got {value_type}: {value!r}" + ) + return value + elif type == "int": + # Expect an integer (but not bool, which is a subclass of int). + if not isinstance(value, int) or isinstance(value, bool): + raise TypeError( + f"{self.inipath}: config option '{name}' expects an int, " + f"got {value_type}: {value!r}" + ) + return value + elif type == "float": + # Expect a float or integer only. + if not isinstance(value, (float, int)) or isinstance(value, bool): + raise TypeError( + f"{self.inipath}: config option '{name}' expects a float, " + f"got {value_type}: {value!r}" + ) + return value + elif type == "string": + # Expect a string. + if not isinstance(value, str): + raise TypeError( + f"{self.inipath}: config option '{name}' expects a string, " + f"got {value_type}: {value!r}" + ) return value else: return self._getini_unknown_type(name, type, value) def _getconftest_pathlist( - self, name: str, path: Path, rootpath: Path - ) -> Optional[List[Path]]: + self, name: str, path: pathlib.Path + ) -> list[pathlib.Path] | None: try: - mod, relroots = self.pluginmanager._rget_with_confmod( - name, path, self.getoption("importmode"), rootpath - ) + mod, relroots = self.pluginmanager._rget_with_confmod(name, path) except KeyError: return None assert mod.__file__ is not None - modpath = Path(mod.__file__).parent - values: List[Path] = [] + modpath = pathlib.Path(mod.__file__).parent + values: list[pathlib.Path] = [] for relroot in relroots: if isinstance(relroot, os.PathLike): - relroot = Path(relroot) + 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) -> Optional[str]: - 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( - "-o/--override-ini expects option=value style (got: {!r}).".format( - ini_config - ) - ) from e - else: - if key == name: - value = user_ini_value - return value - - def getoption(self, name: str, default=notset, skip: bool = False): + def getoption(self, name: str, default: Any = notset, skip: bool = False): """Return command line option value. - :param name: Name of the option. You may also specify + :param name: Name of the option. You may also specify the literal ``--OPT`` option instead of the "dest" option name. - :param default: Default value if no option of that name exists. - :param skip: If True, raise pytest.skip if option does not exists - or has a None value. + :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: @@ -1629,6 +1904,91 @@ class Config: """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 type for failed subtests (see :confval:`verbosity_subtests`). + VERBOSITY_SUBTESTS: Final = "subtests" + + _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: + + .. tab:: toml + + .. code-block:: toml + + [tool.pytest] + verbosity_assertions = 2 + + .. tab:: ini + + .. code-block:: 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 config-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": @@ -1668,7 +2028,7 @@ def _assertion_supported() -> bool: def create_terminal_writer( - config: Config, file: Optional[TextIO] = None + config: Config, file: TextIO | None = None ) -> TerminalWriter: """Create a TerminalWriter instance configured according to the options in the config object. @@ -1712,7 +2072,7 @@ def _strtobool(val: str) -> bool: @lru_cache(maxsize=50) def parse_warning_filter( arg: str, *, escape: bool -) -> Tuple["warnings._ActionKind", str, Type[Warning], str, int]: +) -> tuple[warnings._ActionKind, str, type[Warning], str, int]: """Parse a warnings filter string. This is copied from warnings._setoption with the following changes: @@ -1754,15 +2114,17 @@ def parse_warning_filter( parts.append("") action_, message, category_, module, lineno_ = (s.strip() for s in parts) try: - action: "warnings._ActionKind" = warnings._getaction(action_) # type: ignore[attr-defined] + action: warnings._ActionKind = warnings._getaction(action_) # type: ignore[attr-defined] except warnings._OptionError as e: - raise UsageError(error_template.format(error=str(e))) + raise UsageError(error_template.format(error=str(e))) from None try: - category: Type[Warning] = _resolve_warning_category(category_) + category: type[Warning] = _resolve_warning_category(category_) + except ImportError: + raise except Exception: exc_info = ExceptionInfo.from_current() exception_text = exc_info.getrepr(style="native") - raise UsageError(error_template.format(error=exception_text)) + raise UsageError(error_template.format(error=exception_text)) from None if message and escape: message = re.escape(message) if module and escape: @@ -1775,13 +2137,20 @@ def parse_warning_filter( except ValueError as e: raise UsageError( error_template.format(error=f"invalid lineno {lineno_!r}: {e}") - ) + ) from None else: lineno = 0 + try: + re.compile(message) + re.compile(module) + except re.error as e: + raise UsageError( + error_template.format(error=f"Invalid regex {e.pattern!r}: {e}") + ) from None return action, message, category, module, lineno -def _resolve_warning_category(category: str) -> Type[Warning]: +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). @@ -1800,7 +2169,7 @@ def _resolve_warning_category(category: str) -> Type[Warning]: cat = getattr(m, klass) if not issubclass(cat, Warning): raise UsageError(f"{cat} is not a Warning subclass") - return cast(Type[Warning], cat) + return cast(type[Warning], cat) def apply_warning_filters( @@ -1810,7 +2179,19 @@ def apply_warning_filters( # 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)) + try: + warnings.filterwarnings(*parse_warning_filter(arg, escape=False)) + except ImportError as e: + warnings.warn( + f"Failed to import filter module '{e.name}': {arg}", PytestConfigWarning + ) + continue for arg in cmdline_filters: - warnings.filterwarnings(*parse_warning_filter(arg, escape=True)) + try: + warnings.filterwarnings(*parse_warning_filter(arg, escape=True)) + except ImportError as e: + warnings.warn( + f"Failed to import filter module '{e.name}': {arg}", PytestConfigWarning + ) + continue diff --git a/venv/lib/python3.10/site-packages/_pytest/config/__pycache__/__init__.cpython-310.pyc b/venv/lib/python3.10/site-packages/_pytest/config/__pycache__/__init__.cpython-310.pyc index d1e29062de3ab3be13e8da8f897f6df92b02bb9d..53b8d85b1f54e1dce381c198c0e98ef1035ff2d6 100644 GIT binary patch literal 59674 zcmcG%37j0)ec#!4_w*bX3~mA>o8SQq1q^sd6gdJZf*?SGA_ouwl44VIPh+Yvm_bj^ zpt=X(w0mTdplwMuWm)l+$Wj0w$ugTbw%3l2T;8lViErm{;;m%YjvXgf65C0%N!GAP z?DzM7Rn^@y16uay6X4S|ud3dA_0IqO|G&lI;bMY+zq|Rw+>d@RnfP7Z^#03n^LQeW zZ0^k_5?-R5@RDAto?J+kQ%R-M^>jIHe>3Hb{mqth{LR#}3;A;1@?=RD$^}d3NDq_; zES)D^EEg?ZAU#+fwDbV!q4JQWi=>Ck!AYJWGEH}iL}KDMx> zyd})LwY=5dZ7XlLzdOo1?C;LX)1ZDC!v zmv86UaQ*g$Qn_T$M(TGg>@DxL^d{1GmhZImDCxV(cUgKf>ATB!TY8N2J>`2Wy@m9? z@;*y%CB47A-_q&&czN8~aBulu{%)(^yYPYX2khN;(g(^1EWLyD!SX>%?<74@p0M;T z(uc~2NZ;b^uHUzCfBAk(@2Nkq@L>5tOW#WRgJou#Jh#<9wD3^*Axqy*`orZ9lTLc2 z`ojy4lpnElx;|Ncm}hs?Cl@|a{)j!>OZss6u%+*;Kf3T(`7ul1RsZP1H1Ulopk+a`#WAgvG7d!8GCmx>5r8^ zX6X;qpIvya{G6o^ke(_}S^6O9ljW0^o*;dye9F>?NPoQiaZBGv`uXznmcGA!df`m@ zjHMr_zp(IP`9(`VNcyGnOUXoS|L2qbx6$(t)<3cEa`|O@`=NSy;g#|$mVT)I$%R+T zuaff{%9Vjc?E~J2uO!RUwS)GzTD$wHg!i!b$Xf~Tk=o|@vsco*o#EM}_Yt0bq~={o zmuue7c#nHW-pZ8Ec|YqN_nsm(>-}x-g!c@o^8@LDwD)(szw4dyKK@p=Jm>vA@3eP@ zH}llVdH>M+F7LZ}dzlgV9`Ad3_q~k3Yqi&|B+4t^ zKk~lleLrv8y#12*W!`?dHdOn@+TL@iT?xj!V|D&zufviuKys&|btpRyMH zg7?GTU!g_c)T21m%P8}{ped6O1w_etS&?c#E+F=MsW{pHF`b>_Toa$e1=HCm*GjxRT6 z^!YO@OL~(&(pce7W~SQI>+JElMzyZQxiFDBagy8Inc8bDOAViS`P8w>(Wj3c{n&}A zr+78;^y1=$xrL?rb5;LBO>G~Fo}652HKQEOrP@q?4m}BT44*czNV%Qobu-yt*2}CC2oo@G%0b+_ifMykJ@a!SgkLsZ=1Xt zgSfzaRhqTdvhojBDwk@0lm1sK+zuamZKh^Zd7^P{kq4tpBxNcszdBPpTb;SUvyn5= z&FR*P&D6*ltF~Hy$)6)Pd4{UZX3cL^8a1ltzHr2!U0!fQK6w1Zv&SkYpRYW5;(1=> zPE~#KlFFQ`&S|Xkr!UMcE!8}dL)Ej*#rkrqwnW#tA3V!i^66`M%Q!FcneeuF!fu~i z^wiz0evPu1YP_Afz^FvI2AkD$wMy-^xu(rV_H?V-RNY%nt!SmXF?*@%H{7z?%z!t@ zzEq!^nQK+f*~o4`V*O*>p08b|(`410P)qh`xW-1KL3zHWHrD(gw`3of(c16*r|ErM z#K!>=wM01y0h02P5F9CpjdVHdWgs@P<-C_G7rcCVz$=hvfNfX|3ddfXqsd;a{r0iO z41b%Y*7;iLQnfzkmFQ!O9jJ$A*?#u*iDI$DzoW~(&vevRO3Ms>)As6;ExKlDp}JCP zE-x+da*@jX-g>Id(&bvc9@a83Q3{t&)niLdPL@72ai7)it5g0iE?-IMlGY`o3j|ie ze~8PJnqR3jstdJBB`8)Z3ya=zUFo4p<>GR+9=^#}D&FD@k5eZ<77U#^cKXce%JCzt zp(lgE6H{l7J^%a*r_OLQVmDJqo~`ictx>MUS3*_Ss`cDez9h zKd!dua{ZM<=NA`hhb~l`3kNkKhb}Mr7n-b`+M(k|jvYC4sn)o3s6Kc05Tkg0vGL%< z{r4Seve*xXi>rC4;>P!ogMf!BmAMAXr2^pdKg<`Z{F-B23W-cAQ{Wf=FQn}Kx>cbO zdt9rcL?T`l!(QGi0EW}vh*$ImNoBktR>G!Wn-;DXu-gR3*-OiQEy!qZTp!Yv=1-jB zZ;+j-(#K#R-t9ro2O#*3ATzbtsLg1`^iOlBX6XK?nlehFlk6laiB5_u3-xs3o#fPb z%0Iyy|6^Rn(?O5j7h6d5$}#pZ`-i_6H(?M&BmWmi=ux@ zi*C1;?IqDlbQ1H)E6MrP7oqQz<}dA>PxCkZr5wYQnYx~uEVc8c{pb9}h0^up_;@DB zw1EBAc>gffSj*o@`lpnRSBPsnYmsJDDsSxK;w{cJr0a)@%U-i3Gh$06D65)s(aYN)D1ZsHXjB4@HwS4LHp;O3}bX z`@)qr6ft3Wf`PbQ%-|gigRdE~xeT&Rut6(GEjC$}H4v%)3BBu1kuCHCYmujr2Q%T? zQwcvQq{l}6G!@v2x~NC!#wuA1x%XxK`L_azS!d42+=HcM}kXeF-N>|9H(rdsI^ z`(QQQNzcQrGG|%sgfFGNw3m4sj<%DU&t1*83fGdIR3)c7e;?NYFYD#J{M)HUx|6zE zyp~wav<5p+f~(n1_Uh2J#FL3v2Ul~dwUfI#e2rb@>bv-$Ai;QHs(sf9L%~LYX!0Qg z>x)$n{1}shrG4$xzKJvMKy3LnkFh)tdOSX!A|-kP=ybk1*BBr1OY|xj(MARRhIEPB zL3ip+MwF+UkAZmQ1vxk9ZVdCYHK>IpKgd(TVxzuda~KqD`c%9LX)5)r zs<}XJVAQ$AAl16;?yQy}=5L|eYSp|->#ToX`PWSO?j;dp>*ly7g6uDlzqyCYP_mHR znam|q$=r=hcJxLrm3p_39ZGIZW^VXR)v8>5v#tFY&-Mg2>`BeApj(nz8D_3i^AHUk z!MdKAzL>X}$xgNNh7~7EL9%$pKT8$kY2S@w)t_xzZ^kp-LGYKz5~LTJvupdf+4XT* zlxD?x_CsWC7Pu6WMS4arE_35AbGd$J9Xjh3#`x7zr?l8K1oZ6OgkYrMa}9XYdCb6%#EXDqW{;(0RBD$GN0wTknDSBJbDdo11l znf7N}c|GxRhZF65YZ#oEypmeYyp(7h$|PDND|5-jYBrf@-`U#K$x`d6_LkMP`6W=- z%d~7Y`>E_piI)-`q)W82VqX|z7bdTf^EA(+FPsi@Urt_5w4d%|=C{mm?PR>d+sV~j z`_@*Wle?0%nle|nT?2LZ)cV=?tCf~+U-|JQC4dV#wTjlPuj8-3Umcg@5|$#q29xf-w2dd99?WbzKb}NJ}*ydo!dOGaj zFE>I|Gr3gttK?w=Od8Y|Y5Xh;wzPN-c-6z;lEwI!=0xelxze=x!rGM5{+_>-*mRNw3(%noO)Tbb>+ol&!0YVathi>U=#qK6SA9| z9p43-TxkXw^&}|HAp&T&s_^ChfQn~l7MB~XAlGbpi_5JbD~77(-_9$ejQz)zdQ~~| zP~mg_Vk1Zc*n^SE5?~p2*nPz)AA_G(6_Ck)Qiak>i%UV~JnUJp#WlM!v)~E(R~A4t z!Iny`(F6lmkOWwBMK%|WX*M)w(AXdvNJ22pUs1J%M(uKi4mE?Z#kv>L6A?Gl>vUHk|XwiYqBsh%Co}jsXs_%MtELK4rK~i zJsbLO1--F!>c17!DlwKUq{94vTp<5Q$}mol23XGUmH3~?Rsm~+kfK1_z{V?y)kF)* z`D$9QqXP^$t(}}%L6)okEg)hmJD=xQxRzQ?azD_49;5_xf?$=P*=g;+RD1CZ%lhcE zCyWY&@igj>9py$pTySU3&oxWuLg`D1w6@YhtqN!twa2cQWp6G>q9ZErBSz{?YvA^e zPqT(u(^b!{Pg|bjgVtaF4SM8%GZ$nw_2uUIVAB)+;)Pn{)ZCI2jJTzgSzM|$g1lF| z)L5?9gTl$mOV6L2diLcY<1H^eU}QwCu?SU?5$WuIpK4u$?1xw`NEqBQ`b{zzf0xST zm?>s6Ihr05mj4s~OT6ow0`85edR$D^R&FBxu9mJz@CMN)!aB%@3w3H8V-O6ngIaSl z3pLnL&-J|9JWCme@MS9h5B$C=3Zek%;p>UxNJP&Dc|><&wEK{#mWT@264^^a$nz@1 z#0)TodSP z+xeZ4W4%V-)=g{F1nurWB*Ry8T!wOE$xPD!VUl0(P&IEpRF~dws2qw|^jH40v5GT( z-Gg;&&tS!+k~-kbr8zcuFkmuKF$rOEsa~ZVRV`hfLt3(Bx#3smnu13$Hxg`WofoR_J}9UO zw6_`L>x-^iG)w}(W-w&*!Fj~uaCOwYd{*sf(qNO!@hrqo2wdZqH-ph=tPCU~K)=2< z5{CB?(Yd3Bz4}(CR&LBUl({iTpAy{eB$f(n;$#bM?rIiZF7f4kypygcv(li!f)i^2vcAHWB|#;#XCD006=caR?R!<9weD&nxF$V&owSB&KS7? ziw#(Biv1g6*y_t(t)f9k7#Ip*9kCzEzWraI(ZT46xcn(A4#&VkKxPnR1UQ29%w^9$ z8*CO()`W({0W-HLq|7QI)f5az+`b7sVeh+Gec0tevmwd5|0{gma5+LP{}tt~?|DCV zvu|sNQH;qT(P9#FX=A_;4!00-3ERykR}+oR_9k;B)w~T>B-PT=&yhDTvDh`lVQ(g3 zzr>KIOyn}KnrZLtB>l%aV$H`^Vf|o&iq!isjMZScX+A)DsGFW2o*(ICU{+IXy<8_3 z5@4HFP~Pf)Cw+DF8h_suiItveKQ#>?ZcdkMJ4vqt(as7*HX;7Qu%U)Ri$wwsAQE7H zu{Bxx#ykAg(tcrR(yQZ)x(&Ft!1!kW3V$4dRM5r}rw%QIAe>Exzy;VmAWfMO#=J0j zi2s5Lb?46Lim~oKv|UDPgF?iM_}@;6V5?iQF*R5LiyytUjR}bCoUs6#d z0LH_Fo6oucaPXEn2u!#@)$xpmAbJtT}(+3lR+c{c@0sWBb~dE zOG`+TN~YeklKwLkUneBhx;rWgP7=CK;%eer!e>or^BP5zY#)BAhHMgwB}Si`k1CVK zc07pSQj+r09FM|nSncMV5dAmTtcV(+Yp^*|SUSqBzjH7`+8OaTqZ@|;hJo9 z|8z9r=b7YJv#Vgx`NS7e7gMJl&;qYzMCUSlsfqi7xcKmwkKnana_9Da= zJdIir&NOq}9N*+`(YrjXlK=$*?xqjF4IdmxPKsR94@Qn~ojZ%dHQeE7*J}+g$jrmT z1^JN64T>Ig6GC{PO|a$ZlP5n`dG^>-M~=Q+dG5&bQiJ>dhA%k<#sf-?=rY7*yy*W& zrT>#If50UeywF&@+yI`_+h9PXe8s3Z_%o44(du+5wb2*#8RQorgoL8kY=!L`k7HXy zJwd@tb|kf(Ev^l(Exwg)Ur3H}EgJW&O`qYHdv_E(?hjCIhGKpHL{RZB*~F$R=G;tQ zTmII>IgG-*>{~50V=3&Uqj59wN2L zn>C}H42EOpy~Ez4JlpKec^~y2CpG3B@t*j8j9|QD-to6` zy_AW`a3}g`lG0%T>)Dn5r;kk=N#Q6H&jZ9S!Xt!ogM+3KIyW~HOAU}=wN@q_&2n$) z*-+2mumG|JY4`+udj)F;O4HM!wCdEeFFbW(>h$z9Vv0*}tNxtH8X(W@`z3OQ2r8_R zoU`@Cvot8m%tsJidC)QuQ3d7&*T&?Oo7)Q4v7Yv3Y*%>J~2#>p$1pI=>AbVLKcG zpc8($%Hdwck;l1Li}D7E7bYvEqgTMSK^A;CD7C{HdgEUY+d3$uUW*T$uJody3Pock&VFa@hWR_d!9(KYawA3gGfsbdG&z-A@KJrH>I(}!cIZ_SAESRr- z?hmTwEOrsF1=7k&^VcGGG_^5zv00c{82$mh;ww$eY?Pc?Zt;ej#$r^+7Z!C0dXurb zsFuRj77SxkVW>dszUf=f3PKIWiTth#N+{}ot1h3`6y&OSI1CMJaKN_?l-z;2e)|9X(bF(n#9zrS8k#~nD z&DhvDf&~XE72`}Q6$hh*{A}QUh;}p|<_>a8Qp`*)lS}1NBiUSjC?z6HvNK38u7bqK zJ-;@TBXt8#Dp`PFYwy{B$%zdG{C}@r4EJe{{Qshli!Qys}b12D_GQ<~@*nC*`G45)CWeV!=Z1z%nBeMcL}5pj~e1U?3+MSqDx6 z1*#~S45ja}63`*S4{V(*%A-Ojdxd(&bJs^Eq0b-;{kcnE)k*kU!Ex)b+A24DOE)p!5`&X^Io{F&<7JY>=$Zrd3r| zgmB%$xlXVBKhZ__&tdOv+`=i_glmM|kfPm67)B3rj&|{}2@bh8n{Z5X6TivN6raUB zXcsg)!bBqKP6_3vM5Ep)rrY~An$!)N=w?uR;Om;fCuyr#YLw&Md6KHFg!(U>BW9Sn z1HhG98RiG2w#`qrmDBuaW|^a`kNI+93PUwI_w`3ay}IDImsv%{cp#GprqCY?~8bmnHhBarfX;3n(wjG85i)P5kuVA4j3AZTG(QT%Os=b&J64lTLT;-4 z@o!uWk!|Tb)@WyIwFYVfe-_5qL<$b$`?Uk)s>yc5uPrQIGAj|I2v!!CeV$!hMiD2A z#O6ehtXxkXgmyN_G3em0@kEfBPR;;4%Uysjoo$Yf86@^;XCm1B$8xq@PCeB3Nj*Q{rgE8O)M!Taz!*>S|R@L>bdd4JNtHdC1RAJuMtz$>y>D2 zp`L&zGd3MC4g-gL^*OVf6I&i^0_?^w;cIQD*oN828s8^w(Q4A~sOT5*17espv)C|*cRI-y426EQW`U7w+F6&OXGqWTUL}}l;5hRK8`enY<*EF$qTHtZhB<~Tt&?08% zC)<#j4$bKkJfgE#q27* zmG*p9Z5l~!+?6<|lXOTzdh`D5TFc7%hofP7shIx7rQy^eL*ibPXd&NA6D z(0Pa*T&w!1!$~YHVjK?XF;VJns{^L?d))1g3AI5@*dMjdSl@9rzS9P_e=J0W|I=D6 zJgY|6!P0;BHmmfSCZEB2G8l!_L!yP%?sBbSW=(rpF7JfP<#ncTEuT9Y!V3|SMvdLU ziZS?~$%tmkya!Qbe~Y>HT^ntPwH25U{Q90X1~{T)BfHU8rM?ji42oR}7NH(gq_UZi zEGTv<-Rn>o-O?dik?1bg1&ACjGNGtPfLFEaqbFhmZnA@mg55Dd)7~f2o3BpOZ1JqDUXx{Z4df*9Yla|2G@*=WxGT>%KiTR!LONdwcSm4N6L*{wx+Vj;*wUFU0brp`K{re6cTv#~>QCMnX8LgSz zH!h3Ig}K@D*t{Uv)br9gq*K_iEMg3e8BEjhu+}WL`PpR*L#4ogM}E-vB&=>>4`8qp z+fYFTecmB)am6+70D$JQyao*OhARx(YGNR06f>ajhI3# zHaYo~8~np61@ETBnel>uFU!Nd&6F@ zEoFA**GC;K+yzN9A`)n(DR(6X3s2o8X1lc)qx1zk;vy#Uf|HcMJP zy~FY&1n=g*I3{Y@dlb!5*<_GY(yP}c173DwzR$6kXM>=^gxjV61 z=pfi@2E>6}+yl&}{!aFAG`8)*XdvaCp@AMBFi>EU7=Jqt zq^pv#Tg~%p8qZl>exD8mn?l3ws0#l*o+8&afMBcA_I-Ek2bdw`?bvf*=_VDL{}t6E z>mi#=t$z${vEu0`3H>>~`JI}gd&r+F7{|=U&LF^+(7Jz;ifr5^ z8=OTAWPeAozyqDEY2S~j#evJ1%8>(2uw!JheoOsa z(8XkN@A845@Uhy8=~W`hnXxt+76@+}{-!T^=i2cZtH9o(7eRDmu#LPwso@!=WD&#h z6lx4(+inz6H`%7^D)2Azq3fe-2dZ!PN>`{IY(w+}`JsPgbP9RU)sASJ_|--Hw!F9*Tm z`OT}vPLXG0K$_3%@6|2l(Qx-CQ>%l}d&SP+{MJi}=H<>{r-+E}JE&)fyq|4t^JMad zVy9D(MR=z$zn#CiFXhenZRqL__l=A5jLS|`kA*L$oI;YX?4p(|R@!P$flV?n#da`J zEX&7{2WWNW7u3G)ELp7^?{rj~9@4nfw zz0NkoC%@d5RqQX)>a+!$KtL42Yp28|Z}1dzB@OM{4mzt5ik9V@C1FX&@oN#?B9*xy-tVWCt?Ln0JPx z^~K9Zp2l@cND&&>h_(2})nd3)BEcg2qsk^(Dy?ahR#qt#iik~S%H?N31;;UqU09?g zax;=s5pw$jrAwH`x0K_2tF_deJah=&bJoo zce-o`!;NqduZO;&Iv91wob#K<*|1VF$d`LO7vvEtnRZ=*2cK{c34cEm>lFDSz#~bb zEnwRQ5(BSYv8&|#A6l_8YA)nbH!>+Ci0Rb3D0pt%GBEUC+xMSZZ=i;I;`?mrW9)m`UMTI^P6s`4lYgk;_qfhNET8&1f}RuU|{qm=^x%Ck2c8l9)S920goLz~C&`Iqut+t|A=+`kHIzgzu+(>Siv^ck*7j44fT6o+dU<|qG`ZmqkBQzcdyHM=e z-hzx%VJ&2Z=Q)dE&;MES_g9emBu|6lSdqPxGKHX5nfPj}l&xmPn@B6krhRawHI zVFhn!o-ug8kwM?886Ud|{}(sewTU?nMj}At2oqnnH+|@XFLpVKF>V-MCPI9K4X=2K zv>V?4%v<7un4%==&iAQ1s&p;zd6Q>H)Exj&Vl4BgEX=5teO4CK(8X-qt!Y<7zZjSt z(?dy(8#XU?dbOPMt;3+Kq^wMHP+Wtv7zqZ5hl_o}wG31K6Hwmo|c! z@wk(@Z&#`BIetf_XX*Ap_*o`+TID@GFsv*8{lBE~6sJPkAcw*EHS6UmGJTAe`QJ}s zW7;8ZS2+1nlwLn5eIf$gwakW-@pA10?S0)5fQW%aiJK6j0~X-V^5|y8ideTU!iq$S zq*SoW48eLQ0h@Yc>%d6c8=+!M?Q{NIgO2L430pHaHaaJ@vV>p?X^~-w&^G3^!)kiJ zY}}nFLjSuMxW2t0;YiG45ev-vhh0IcIrIz|47LMS!ui<9(f~lVQ-DkWDC zt7EMJ4_pYq#g=lu=oNO!-v{p`SuoEJVd|5G@k+^o^>UJu!z-^vCHdCK)lKZ^?EL7` zyf<*pfa^n9kJz77Sl+4Kav#jd$pjx&2oho@LVFOZO{>gCFh-m6 zMQybYk{Bu?m!-F#PwiCGMa4Pu@SuN+m*a^9RbWiE-ID}3=pVfOKkZokU?=$`v^r}1U;bQqdJ+)em1X=Swif=Sh82RAH?$M#*u41;BDQ+u1SlmRqM0&XRq15h?8{F+E=D5#n&BM4Dk)-jt zJ|Glrer@XrGoXSaYd%Q=gCXgga$Wsa;7+G)CQG#DQ{!w2WQgtwFPunA$ep_7TJ>&XM{mrjLB zPU&Ld1JuL0b8}!6Fh#Z03JtglQ_QDl< z?|0>Bws|69txhYNJ{k7~}=P<3DCS%?xP(|NAk(!T-%oMX9c`!Sl58_kFW-b%trWSE>X|0<}>T`1`;?8+H zgDekEojh~;*qO&%6E~ug5!ay(Cj!$K)MB*&Db^i46o}D-UBbg1$UE`9 z&4;}Lb!T5h6>)K(lktyRiM&dz5-f~&`S4w`xrBC&B~Lqc$nNTDK^tQgIXd=v0~n?b zi09@Bl5*ceyT)H}3>Tm46nvjpK!q#GWAM{Y^DPCSi+T^uwQwoG3pFZSj7OYW= z*koXi&@%a56J7m`F~-i*!~|b1_=HK_oRPy`(g@Co4YXW$_V{cfGfwi%ojf+qhT<|x zC!_-{X4@NnLVFaUvD=@$nfD^0Lm*eO+SYv)%R{0S zHRhTN8@f))KoQxXSHow(xnKe?kj$OKLl4tLB65fyk-v@hteHvFW@k5gXHD4eEH*^1 zB38zD0uAK~9^F`RFtoCRK$ckATf=ceGD0;SX<@zIBzTBb@HEB|M()aFt!H9rW>a>Q zHbH9B&Dt0`TWCCPGNAUIb??KtA&4?JeD46=P2Ns0B-Wlor#|-7!4p%*Pk#NKA(Y96 z=2_pTH90oum&Q&1o;`8&*wpD`-R~=0r-sL# zyo86XtY%nQEyYUHtj$;q6&5Kvol@&EYp!QymCn_xmllcgNX|tc-)OfeonLv)*#TM2 zZkxJIryUV7Q>;|2thLJ@G!-ta=hld_yU}zaN%kzt%jL5Z81jUpeUQ)_QPKw9R87r8 z_doPuYs>NL$wTcloi>M!HjzqYH)8=6As93uiW3>F^-2Xpx*@}La`^C^o%>sY>E$y`@qaG)5-PLI* zs{bGLI-D*cP#33%u}E(Tqfch>OdIls@dV3xd%R8FD5<=6tGC%3BUSKj3%%I}yxT)> zwxYKSdY}|+uRIyM4xVVt&77M(r8u_j4;{rL658tQoP4^ldE=q2IYFvO*TFJ8ioR!V zc7mlq(TR7^VVFjIZgwL^s!x(6T0y^~Le51J$R`M78Ox0PUt&Cb!fdZ2kVJ3)mIhqK ziBx&}#(juAP=Yi;DIDi8Oo^MfVnnSlOQ}wB7b>$zdX@HnnP$*6*UcbnhM};NCYk${ z^-aVrN8&D)?LwvUJ2cf~huBT*y=hb9?EbG&-;Cxx`locP-n)ex=JIouB6}* z$dblsN?(8zC==wV1`BE%DoG4?ISixM zQlxf*KjnD>WzUm*4QF-Yxg&h74SEA?IW$n;pY;B^C%55Td8jt*{S9xMx80((k8t-R z-cE0q-EHFTZ#oBJ>Kyfc6i{3OIB)iT%)7(eOKObNo!(ufws=48-Q(>ewUv19_j(^7 zwQZp2{e<_E=47133HWb$AM`%N^BuLFM1ohue+8=A<^6Q%UVKY!H~o2x`0-(^f8Orl z3r~1QDRnDx|DB8RZ9IF%`xwt|C*_=tOPmF8(mTboJ2(g6CGQiY_F8=}Q{Uh9UhzI@ zU%QhMI8O8SE>ab5n$+E-s@_>r_i%0i6g;VYoDVSTohP;5`#ECq&wCeeR37*K0Zz*o zd3rAq`CHyHsSkKR?_Ks@BXxj?{2gzV)Ip-}e~NhgneqgY_OEzvkvc^D{BQQYh17lC zFLhI?+c_q!EfjE>AGM7dLI45MABzczWtc{Cs-`?eh?e4a( z$gKYD>!oUV2_D9L(94j*u#{AeRNhh}o_zi@`1}tu8JpTWLZW4DhpRut1U*ggKwMwC zy&-y)afU8d;|9G6M^XJbLw{Y!N32X7BZ=dH_(MhYIjFcwJ-IiE)_%X6VO7R!l-9M$ z9^Ocgk*$c84y{@35NEorqInmb*{eSM@>6!EhU+-u5_5Blh^|*#wCWO(RbqM!r_FAIM z9<{&uTE1Ke(+Iy9kgY-H$n#H~)_Ddmp2WA5h^jg&L-$8dzSzAVP|Vd+ zN6tKbx@U4@nk6W9J9396S`?>9mqJc1*h7Q`(z2D7P3Hk%fHmd+GB++7lqC8V{OtOU zwW|Sjz0~+HrN~HA+@?DaTc`1egiQxCayA%5I)*Rgj9-ZKMX3L#998 z(A!Bi?CtZV`8&w9*TQDpX`6J|*}nU~NSE7N@7}LiedEp2E1}2ItEE@kXcGBw29~A&c%^o)&L$XP8enUETRI#xei(?M!Et)aXf{arAHa@@w;p*`2Um-`~OUr zf6gT+ure)cFfaw4G{-rhhQ$YPQSJ6A?>)*pNT@tJnMW-k#!t@|?$dkh$~Yk<$YHUB zn8!?HeKTJEw5kw;=zN%IJZ!m^68)gA2RL}7We$N-jVo9mCr-?@PPY($5VL9qYvac9 z618&gv{-xTP!PE`I>dos)IX|~`lK$VD7A(A@04|rULt$3-D;32VlIDEjrtW`Y(v;3 zhIey__t(tGj}Jxv|s;VicLu>aWb+$d8Fsds7H6 zBDs`f0%%qIPigH91OBock)Eo{>?N&)zY%+tbBzDI8mbyF>mLq3=iV6hnOXZW-Romt z&5>Zy3VS5mA3p=MlBKZ;3`tM=wR4lxp*?EpaF}?k*OX#C40~we{uqWqH4Ri^x6cha zV4C9wtvBv8mx~80E+n1xP22A$=`=0>#KtXmyTQcQhr8+KpREH0EmpI?-k|*ol4UP1 z(FZ?GYuY2-@mDF%W`NARK6W%buj-XoUOixNvEO+{lLr!WOJFpd73W$GsDbY%TJjr5 zaMdJsZ4?R_T3w=7Dn{Og$2kELnKakYh}uJX!`T_j3hx)sisb+Mmzx}%gFE#M@w#T_ zj3_q6Lnz_q8zCD|3j>M^W61#QT_z+b=&N?jQz&c7(SbEvh)qXansD=eDq5kVnf6#X zLOrk7%`n;BSlk z?(PDJjV5!L^MgVuAz^FYQj@ho6r>rk($=uqeVNuZT%tPL$AWq7`?R@y4p0OO$Th{u z#H`V>q!M1BE9pw6fqptlU*=7Fm^&1A3U~Ap5hSk`3Ht_X5v zWX(HMsn=zTa|`EJ3~|@KB^t{00oCoOiM~Y1w#HgH6jW>ILP zOm!pSqUM!(zYn-KNdL4JZ@SXD%tAKecVvTgyG1@!6k1T+;ff1+^u6j;k&mlKnHxiR z(#E~mAiJY}e~hoqXje!7w5kQ)ZGpT+qK%IADg=#mr!Ubla~Mmm9C1ER(EglBB!TLc zbn_Mr59IrJx1>Pz%tCfGjny)Qj*Rl&OoVxn6_NLYnBtL_zxX;*=5gx5kU@(GvV^dP zSCJCwn-(OrI2GhWJ=Jl)L#t#E16U2VqTsn4MXJ}|vn&B3uRm604*=}M3`7ql+Axb#lKmXZ{ZTLH#YCaj93MR;q78*{@;@G%bMH0 zBvJ(P%xoL_Punx{j7|*<{V}nmEYzpwJe5iPVS9JDa>K0?hFy20kR2W)o$d8Kaaht5 z+`jTeZ=ljwt%uilMReChOm>9?bKi=|(=5TDc*S6%sptYGX1|j52R&jjH?VleHXu2V z(Wbq#ReT0K!%Q zH3!1;O!Ta%b#w86Agn>z!Kq+Ckt5CK?>O;Z@uoowg~aGm?F<2=ST-&kCB!QsCL1#% zzx>-Yfi*eJf}bWK=r#|&m``E6A(wCf zz_sKz;C^7Xy0HH^G!rc>%ty50RfSZ-IT}ak^bdqk(cqw8rmqs3Q>|~jw;Af$FnvJJ zq{`&ILBhK=8Y8kcU^UA>4b@^W=$h?rELf<~G{c;%Xp?PO*N)s4Vr-)n4Rg2}eo2ET z&T<4YxyWyn0gNH&1_gpa$AjRuKGV9sIXtUVrZhKk(TxZ33BICTfHe)$5m5@x3|i0< z&r{nu=4GjOSyq0*MKHfgWDc`oA!;GRmu}z1Ugvwua>>SMLJD5fG!l$Zh#*8VHR#9 zg49Wo-+>z`0_hTT7w93sy?boo;p3w4*UkOJHZhMEDf%#!10zK+!r%m`n1eGcn*DeI z_7T9qA)HJskzt5aKqw7MXT<;c*VWL^=wjoSM_Ff%W)Cnn5xZuc`7bng&FJ|5jdH)O zK@t5WpGB=P;K~lkxDlb z)JS`+dHkKw0`=n>nuoPeUR27q^5aUq%q0@IS*GXX_h*!{>}GgtIfpTa@GvqYp1zh+ zu+;_DRoZ-Uod|%4b(kp-Tn^g@i(C@qpmRXzYoUp)Wl!I+&)OIDu?YWuI9l3#*1i#P z;cJ%ntszD=Gju*<{!25V(`NJcHMRShg$#$Lh`UxG6C7EuBcn1hQzi`hucw%I|9`f5 zWdQKL)%MYuS*nkTvrlm&E*z9<pVS6RR0ojlq37U66glqJZ&s!C<8`x}%A4Eu0KsWzCB~z37V1gQ9Dc zngn53MlT>b&A( zrF>2Wk$aApehcnn9`9{MNf6eBvUFiF@?cg)dkpca$uTnRZ|jrX6)6ZX9@*lfb>p^psdVOa? z$F7?J)J>OaPsI4BhKaZ?8Ak7=#URKY4^tg(PQWoEV!_DPB*ILByQ_S5P0Re}D8El_ z+G=geBvP1)bAV+g^EE8Z-W|Hpe(=xHMre7%ICp5fzM?@XC?Kv5!3#Kih_*ylvSzpD z|1kNYZD(7{*14?nHuQ9Tibmg}MvqZ7V&7CIo#R}V+Cq|W{{O-JIB*uA=2}K1}-Xok?1P(RW>>*J!QfCIS@%Cfn(1LE@;f7G}!Sx zX1Rz!7;d`djLHTygFAbVs}oyIEsktg423n5hS{BPt_2G0&3nIwL_HZxZAGBtjxq+j zZk)Ss45B?{WAFe~&^z{$@tm9KKkMHRD5q@B>#&CHZbY0+t;QbNXYUvU=}U9fKH#XX zNhV}nlQPr7R~XH}L7zJWC`Bz2_Ey$#qVWhr;{-k8@t_`L)-TM@rnlTZ&yd<>+FLnY za8Ara$%r(m5t|e3VK+yip79-o>@pBFx=p7@|99p!D9U=ZitL)hY4;f3uY`17!(&Bs zX+E7>hwZFrZvWfpLo|%Wjl8VYwFcshRImxLMj1W#s0LFK>!FlLF%Zvq6k#5dYG3AtLv)5*oMEB~aeN1{bn@pA z20~h8@%?|y-s#wol)tN!UM)2CnSVnX*gZlFu!7~b) zqn(`pD3TUM1uC=#6ai9FI8A0FWi=I=AG2>1SM%;9WDW$1TQQz%|C1+;;x^JplUUjx z(zvE@X&!Q1a&uf%n&k5?;+?P>jCAdi4m+G{EG-j+xNC~ z=TIMgCE~L?&rq|qBeK7+8%LT?l%86~%mg6|l$}#DC_qSUVX1|cNGbd$lb8YGl`Z>C zq(1HMn_4tS0b(x7cOX9QAm(E}BF%)+Fm#D{C?Z>BD()$mif%04a-6;2@M8kc@2amT zv9pv8zu9{>DvuQP4;mm0p=QJ>wk;DDbb&phu;w!tEba=lV84FQ|BALujxB>lIER8- zNErWu9%knhGB4O=V;X|HN^5SRW`rK?aWR|!y|$0w5>hJ@r`Yzn8Ox?m$hn0>r7a}5 zIWq=B;fx`BX7BdzI)9R#ctTt4K0cBeRZO?SuEJOJSJJ2g8VaR#R#7Gke{=(wmXX1? zf&s;nfnLUs`OAk<->={P6hs7GWjHHIXMZ46A4Pw=yN{0}3Us?~MpTjr&^M4{dx;lK zf~~`ilE?`K<(0Y|T8kz@L@^w2@Jr0U+*vkKB?Oxz32W@Cc1gY0u1A@W$>HeR-Wfvd zby;IzL2k29j)8?V6m0QCJpth*7!0ZAaV)8voTN$c$2i6=O%PaOzOea^3PPK(J%ivJ zFZmYUfX#a$YT@kW&GtY6ywE-1f`#n>>x zJ%Djj3#DK5LGC4X-zfZ%PeyD#MRNitfgvFM4#|8-=rzlib8!3u_)^k#p5J`_>D0ZvrY`p{P)a2 zR~6S=xNp*FT32qqpBqPcU4wQ9=0kU}K2sJp0S?AVx?NZZ9htV{1&1GFU8=29ob6jw z1b(bhd$7&oMnsrYy~UWn8SHQjAcB)~)wz0Q?i^+dg!*r7WHjzCP|+9EXCbvw#K;7T zh{=$f?3TW6iuxGfXB5b9ljtQd#ac86ZJoiS)l&=D(I8tuSwI?vfu*zc>_Tpp)y;Y~ z1rgF@Gb-*N8ig=JFDaDDpnY()K+L1GT>DlBhypQ^f!LePSJdT0b(pZ$kgUB^> zc2@2ZQ4YIrE*|75f&AGeIlbRS{W>1z?Nqbcmn-i#TEkxPD)x~?hky*0jK%Gk@gcN( z;o6Q*Kyd_dG8n@INui%Li(%IvrSs&Z>GkO8S)lDH+& zJ`e)1E@X`O((Z9yaX}&6L1H|MAV5mrZ0~u_$rr$g*1=c|qx1Uw|G_8587j3#RCBPs zyQ{+jcB+BjR*rAfMa^=%R4i7oRg_pLFY3_tL-z%c^%b8NAm1t(aFg31jZCA)EoMaaCgD5gC8O3p%cpuoIIi-KFc`5sT)&d zAbD2fd_S)uDqb!0KclDpfY*PGXFsK(5g#NPL4#085=Q8@XiRRFnhjlYbcal5VbWs#k<5|qqM(aJu7%Uw!B*Ko?a(6yK zPHi6)9siVBWM>d7nu8l6>$ z5=7XZo<#;I^VzNm3Nw1Y*a+OEVGnfH56l8|fqy@(riUeR}2n>!g|-el&t@Y~97+tSmXa&MpCfigGy$&4JmSM#tM z*gjTvDu6L|iqIyNTL>nPePT-?7H^eRBVV{pt|9j1_V`KBef?@lw&>oljER+(drqKp zQfW$-yixh=%_h7p7O+~imE^nU4fAZfJxlpNq+as7!yBXrJHEbN(#pM*xl_a#S z!W?_&QE4wHKA*JHIBz%l?>#p@^9P0H8rf+4U=jyZSLJ|N+$85GIE&Io)!XwnV+KAv zfQ0|h=aVZRCAv|OK(x183-9|pCpuZ`e!lZTQV+Bq^lt4G=RZi4Bp6M6Aa6HDGWjO8 z5c!DGpDH#GeCV%2)wX3S0&Z+jK_(wC|aJqufz#-%nTZE!iAPXHX z%wbWXFf3#i(5abZPL@}3z_ZZM-~Ry~w#N{lltQU}^rFlLLklW9Itdp!nhIJ_PJ-I| zrV<74yNH-N|5YLfQ)nUHcTfHAqz2o??OTpX<^k2VfS-y-&<%o#MLdS_V~b>M-|-wZ zW0nH@7-5u1DjZR}5;n;)1@&8do2>3EDPFj_LYPgS17BYNPWW5&acL-hp}2NuG1wh)0$Y;`RM~c7Sa^3_0aEq z>z;W&NGk%@+kZqv6RK6n&rpSF56}3IP~G@pr*4)=G!8V#$E8?k_Sn(djGANgx2hK7 z5zOtcLM0gdp0C$lb8gWF!AJWfO~^U?Z&2G!xFoth7!2oG0xSOudUsft@6x4frjXKY zh6_~o+-w75VhjmQE@?4%9WHR3h_h%=ez7B*j$j9*IFx9*o=bZ7j4m(g@*C>L6H5IR z#?J+^`82n|?q1E!nj`jH_@u9>=toubX{BuHMg93P)^$ONOwYetS;VwEwUaQSlYHs? zq&^{m5Ku6Pzf2{Z{2&D#W&Ki@e3=FZTQ?jAQ?^d4>NVJ1P55~s!5`hG2qh`-6NkT3T9n29C`K_tqjhqamZb;W; zN1`Hg$f0{0$`#XN2+PI1$qHJBOh_aMZGoK(%njNoET!K|WrzNRAY5Nd4Q>~?MN0@C zY5AP~cIYbyi==bPffrNg>4gF&hK)O)ejg52G41BJw6+qgWnh=NZ4wN9 z=U|naifFv*kB!m)tVwSY3vz&4g3+}9qt^BGq5sf%YL@FBz+6^g1iEz*C_<*e z!Qnd3_BT|=fJ7?i*2Q076w6 z$QQoAl+ez+YAgnM9vccV{Iw$#07f;tu^@QhQC0mU7tCW!DtlGKBH+ORcnh8g1l7)1 zHeEE@x!L$0^8t-$6Q!qGLqjI70{H2a=+We!cR52EnT`X3 zF8X@rYsDL=nj$UY&4tQl<8+={`&bjPGy-@A;Dk~DVvwPLmo$uIS^=sR4ipAODkrll z4t)py#u4)!Fp`M%B_hcI3FqbOs3Zmck0>Tbyu^nt#k&N{A@%~RlvqUJ5lZ)Pad5-SKG(Ah6EvE5;#D^Hw-Sa$+Dq_+T}K z49v{pc0sM1xprn{M+EoXp~cu@GW1BtLi0IEl2Pg>tDgcRhX(}q55G0Y>4qAi*G+#0 z7HO??Qy&)MnXa8sS7QivF~MV{n=@=R9)IWD^QV61fSTnzOQxqoTRxD5)gRY|K0db7)&91`RmnQngZ;8mot{>hWV!;81b_mFkusO?R`)!#Kg-A8 zXK)1<`i6(|RHL@U?Eoxv9s9wiVxOj`b&$G}5+=GksTwr{&@t1~f(`CfX|iN0Mi(q` zdis@Ddx(nZ>9dQAbxKWA=D3cM>w77oSYy^+yE@zh^6uT+mk}Z%tmMAF=jY&_;x`j| z7Y{nq->4CkO3|Ey*GMxYTt&78$gpC+SgJ$#4 zU2uKFw~wPV6gB~J5cttUgE1x=i%0+r1Ih}7q-%(8_{ z<&o6&TC)RsyRghrd*f)hN-s*~WM+ES^y(ZtrVAuO>j?jILgt?ay8C}D65$dT99o@$ zYcG}&S%dByXVtpV8`ywwaL#cRw2S$W2vfZxgd;16(c7F%8Hw_Zt+H{C{4qo8S>yMm zW%GZV%ZxC3^bhRN)me#Kj`UM@ngK|Ju%oDIQ1Rfm@n#Z#|91YQsisP8P6A<0L*eEc z0F_#PlYw zNurv2X;<>%M?3lED;-Y5g*VJg()$+LUA(^YoESnN%=se6gK}iG9SvHbzBgJ!ox#>{ zYvh~=wAYM4OIX}GNPa`G}6U2&>?>&DY;GYb!L ze1`Fi|0$j3XZo$}BygEj3^B&x0^{Rx%83kR6g)y%{eQ;Q7zdWfZ;*Z`xe<{S6b{#` z3unFRW7o6<*Z_&`iI}Vnmr9R?VVIdZe?|4{OF>!!zJE@Vld>f|>e-avuI;H!X{*$! zr$qqz-^4SHz^lGyifVT}))l=lt@LlH+QDd{QavW;;Yw7%G5;>!M+Y?7lK&~bW7hD^ z84g4-z9*K)`O~Vc$o}`t*a=3!G!a2g9vC%_ac`oCn*o~kwB86Jf}31xvEFB!_0+v2u`iDXYIByEN<_KBH6Vkb~ghvAdOXjxd}K zkXg-rYvP<~q~0oYa?%I0PC0y?x*m#4d@w4(yNtcpaVw|d_fy!!XWmk@26Vk|kq7w- z!D6qZ!unj9LCSoxH4r+b;cymyYl!D(!+M8Fx1)NSms*1!Iuk4^Hgz(9Mu4K1dNajd zBW3LvCI1)V{GTH~N5qpKjY?1c;V{1rgoAOySXtEimiFI#nyH7Hb;#5KP9Z~+vX)Lv zoj@4w^$J%`G~%b6&?G$MLhM*H1jD(SIY@?h>P*rO)`Ak6S*&3Y)Fnm@!-Z5#Olk?| z_99k5a2L1v-_7cn>f8K0Kth#BY+)gMRc5$yGJZ9;n*20e<8IE3yvWw?Li%@{nXzqn zOi)r@Xl-o%N5#WxwyCgxoyGQ5)x9;*zUQbhk`vKJW7qZW`>2SM65Y^jC ztomTP*0wS-hEA@tk@`j2^c%Fz|0NQApI{GIgaw0wV(-K_Ujrzb{t+1WIRTrWrC4_~ zd_(SkJFL@STpugE8fjS0Vo~u^qCX$OEIj zTd|N0WlNyNA!P@Q)#j9l$r7>%(W&h_yY&#Vl_wijv@5R2vh-9lzRB6{m_WNLb?*Yw z@6|xaO;E6Q312PR*;*6@?gr|&d1XEFe_c6$hD+pQ8N*el*)U2+>J6P{jQyfB2?~|A z|5$}>l>Z*79}%E@l9~&Uu~Nfep&zh23H!SI>xd?Y`YyZ0tcbqx-+_P&C zh*9C%_cNTFDass7Ap!%Tt097hBJhC=xbJ1S8YQbsD5r~D>cU1VPL%GvudPaVCblae z4T=o;VlI%N(BT)6lkk1X&k^Z|2Nzm9bc!Lig7uXH04W7hlORXXU-8-(dX8>4l+)fk z8FkT#5lj2pFpl`7*kOuQS5hF(y1UQh0{h&#H$DJ4(LS^;Hz3e~Mj{VN(5Ua~d_+#z zsl8TeXT;mx9M$>%P=Mt)RlCbS(yaj-du9+vxMC>JbK#2izBO1(@hNjj=I0B za=X{HyxY%qKI)|0&Dz_0vkH`kBVdT3_oZQm@TI_Odq;1{5SHuEAph6&c4zNfH)yW# z_-IfF5B_O7V0>7OHz+Sk+5c@_Olvxzm}0X3tvNz^4Pr2NIkmF~+=qr;HwE`aBY-NJ za(ASl0@f}B1G>7WHWytK@b~G=e-sM%X+AVG49GVNEQ`Egc37h*Fm;5nl!!!Q2!EvB zD;6BO7{l;cr8w^=etYv$#5Y^_4V`8PFWd7CEWr3K5lq%SlUlXg7Ucu&L@wA7d0>3U z33_yWmr;FA5_T53X6yW`8wGq2aiZT*(eB?v!mK|O`VXG)S_On% zq3uVSa3KkfKE72vW{a3oX@Rh;fQg>gV!zLqQH^MErpCVI^G?YzMAKp%KX(L zAqNZdgGjpbP|{$i-B99aDsxwPE57;S{!UIVYE%BtLNkqzI2Dl{7h@dT8=|ECDGhf) z#%w9snlmec|64DPD9bk}CB2DR6h;afTTdDT{Bp${(IW(_x+`foCZy2hhz>(A8IdFF7nw63?WIR_D z{Tk~Y*4f86cBKd3gt|(J^(ISG67Gf33?og^%ShLCWR1>43*~^_BO6Nm6R)Cp*gh8L z)6)kJ67O?*I-+u=92FxKR=QUK0QN>N_0cZA3R@CNpC?NQe=YKqYjSB0>Fi=u(y9YZ zm^2a;aqqp6y6WD0IWxCfnv}vhiuoGaTYzYZXr?Ad|E8P{feN~!^+@p$+F_vS2(=<1 z&RhF6(>l;N4THikjN}Bo%ih!k4M4E9ga;9}Pt$7X~ZGvso>v zY29u)sZyJRX-nyPEhZIv&WmEBqE~ z*~u@%BjgNfm=%`2`f0tNRs|2~(j^28Kn%bUxEXmq0tzEVC$u#y7(jxe`yqlsk&B(m zAGb$C>L4ySlcwJ2pVImr6~q=>5h-P*^K0MLJ;vaG^X!VgzJEL#IF2Xq{|#+^C+)P< zT_Cuo=>=L~;O_!0Hx9g*ibLi_@76U=5&&zQVq*wAVNhvaZZt66K6vv6YAPSqGVHb# zTLa(5_;)nMzpKldHjYH?;2aUkFVa%~V*vcv9kBm>OLeENka^!Bv>dwdL`N8;+pHk6<6DgVDR=xeB8*;zE#l z836p&4ssRTJ^}I4%_~Zmf`s4>afjZpK=LvK7DC_*CW(j-S_y={Vxq>g)t}&#fNOt_ z&1o2GqPL^JbdhQ5w%mSF2ZuJuaLs0Lu zpbpIHgOEav<|OQ_Z3N^(k43&LMHFDg7V0`+VYQOKuexqo0Y|nv-*pbJaht7;rs68e zArGVyzv`>XHFqtDioV*@N@F3=1()`Rs0H=Wx^}JkQaBjvYVRrw&vfIb*W$tZQ|GGx z%hBLEp3qeP;YmAYjsb|RwajDEcnyZWzkq;gJR}k=R!c_R&&`BW5*3RF&~GTwf-HYV8F zVE{iM-Tv|MQt8f8H%{xHlKrR?@pb`d7cRS+x>2hwlAyu_nvMr#A*?(FW*2^Qyr(ge zzaU_}rQ0O)^w7$6svuJUnFY{)qFkh=`~%X#klX$oD*{{!3e1Sp%x3MuT5L4j*YS=J zzLa1i`(y^I(+frQ7co}_;iqE1!oETq3X+dT$X=m6Wyo(VQBdqIHfsq3phmB0mH$UP z`*9)0-y;!6LEC6FWaRDdqs3-!ybiU)FFVvOb+J%;%Pf_!^iRRZ5qS#Vl>fi7GmVYw zs^YlkdOWtD$Kx%F6G)j5S|=fKo0g@dVu?~Gq?9y`TvAro^*CD{Pq;H7c(B{FTO?+h^*hT4?AJ8i+tlzm zGDE^JkA_z`?9lSD-AG&5&Se~9%m`;4sS08HS6=A^x_+eUTkO)~{6d7>S-x>LJ=sh4 z8p&%MWu;NkdiA=m&b8XfPeN1!35*GE&^WTyn01zo1o~D|NNClxIk^xkG_3f^sj4lh zT2uT*waqiZ66-_MKNIbs?LVT)lO9P*E$&l_(br>>$lK@9X|@1irdf_qN&QZSz#^)JcP`tz^~(%m-6q6?&9Eu6fWt&i;+WJ(`d>>D@brc&r=! zdi451OM4;zr|kSn%Ci3nr>c$QX`m@I4D>6w5S=-mLy|NSTgjU3V$j5pdy3%wJ+Wha zcix7&Yky-v`+<{mTIlz(34b%|6ehP>o2=98r6v{0OFyF6_fY%tm;N*_{j+2?YvTsU z5Jm>rZN4;_%`LAQHzC39LLbQLd`tz~N!Zh>=gqnmpbKY>b}he6$@NVw@j@r3-G+YM ztr1Cc`Y*R+*`zG3Rh?1=UrVd9@7bJc6#^jYI+bO+-}P;!QkV3!gOt|llHvnJ8-V0C zJzAk?a-}GcSqq6ZV;vXH3;t zE_-gvPnOcgDIfH7mc_ndCGRF)Ds;_0p?M+8LYcMJ>`iZi|B5GQTr)qxmd2ZZhhM3& zw<=o?O1_*n)ECTTs>OmzI9NVX@L{!fP*+dr>SemxN`Re#szZzRXtm;ZC|Zc3%^t}M z<*_r^echx7dli^1nXsh%lmavKn%9+?o1M_r2NcN6+MiZnR$)fgq@W(u+jVg6r6lBa z0Per5;O7c{p}<(?Cv^221;119q=KguXyx~k0r}7BRm4*Izbeow;6)W%D5WhFdzDUK z`*V==+8Hizm*ZR{m!HEMdj?#g^t`*CBNj~L9dKt+H;&i4qEgiDt|(rNhFu?Vy*)jI z>v761x9o+e*t4Dco@fLY?Hi-sVm_LSHgUh1XIDnAjV1?2itCE`Vpq|HKliFoLo`%c zJwqGX{-q}$>xphSiBve}=vXMG=NvHF0nc8Ns59!xY!S@f@^8HUueB&dlq2RQi2S|c z{Tbpe^iKk^v+tI~NsTDz6jnW{LZEgDYx98Y_F4UogBt^zGayj*sm^)X1}hG? z+OvGHWjJ@jOs}mEjz|i`;>Aq&?!fb64)5hu^wsY!kTinimCd?h@CMXIm@mtBEW&vNTL97U=Zm@#tS{>kCc7RT% z-00*MrZEQTfbpQGCB+H{vh;Io100*%a4z6a1xG8AuZkHprS^E7EG}$}Fp8Tkk1cSN zJVpoVr)~kc^jn=L)Rt;&$JhBr`naxfb+00$Dr?CojBxy>-Jr}lT zps{q#RY4pJ#{oGT&}9?EKTe4SD2wfPdU}Hb5%BHKp8Wn=Mj2?8ZnR2k#~N5A(+aS_ zYM4RJUU-eUN_%^3SymgL+BG9hjW?l)uuX%DN=q zyCpX0p*@`FIY33BCDJB33r2)lq_RblSeWe+f?)J4^L|@}vs+Sjhd5G$^+4Er77%|R z`w=b!YF?;B57=p9nv)d4Q`$rC=#s9|QtuKP%Z+Z+USBL*beWWs?50bWQp!Si4$Kv-G z2z6CfE%tG8x_Yr+yOpfo?0qW3OGAw!b*)nEv|CryYFG~{bMW};1w5fx@5FCVS+m%G ze9eC^cd>|ed1*0P96HR2?v}NUXG~@Stw<)BaP&m^1MNsig6Jy$0oIs)V@ZZ_`9Y3l zHl|)A`hOVUwCZVnt20;&W4IbiT(_2Qtn!hYkX`newpfH3!&kbsJfXR-oYWVJosPfY ztqG0D+<1UODbry@8D&)LX3KnwP_x*!l>GBhrDJm}TcPyH#vbGw_AeO3NOGjQ+S?qz zTDs^N-E5H=m&g%q?37Mbv3sw0 z)Mh=QYDeQ^D_E2R$x@-d;gmUOnU!L6FlkVuM7CwE zm;Pq}uP{i97wh(y3e48vJXff`GO@!5U?^~l*$7N%hT9gdxeh-U2<+dJwLcji|3xik zH>}Pn^Vr(hn2g>_*{*$u*CGgcc=B+#H&k z0_0?7R^GRc3MXZ%S+bFX0b--V?C_707{?3MnW|MZ9hH!$oF3?maiG+=%>Zg}Q{8tlu2! z4oTKEXFCI&@eIN;vO74J!){6VOr5MaaQtZab{@&ypO-I7tb0I|VD;`=q#O>9uLWCD zda+%BjjW@cRqt5Wu~@iY2MdM9L-w(@@>eNg(gaOLDJV+@dS4M?Mq6V}9^le(gBF=C zhr!?MGx&9z&b2L8gLcr0WK{8?$c?$X}fmlYudK3u9yUuGMyhNs6g1x3N~T|zw(Qc1xLnz_EF zNGa!)%0EoA{MQNEHiRNrA{e_x2G4V`D8sRB7=+`GDM+}3R>g;5at?L88*q?%=fXG) zLMQCg2ujt!jlisld1C^I?Wh9;rnD${F-`971UKG(#ZE0sz}j9qUY#Crap^m(^mqPW zrSHf}uZZ>NZd|_$4&WHv0Bawz@$D^J%y?akhdkp=0=dBJjZvcp+ZM=`9R>%_1PSvJ z(xD36Ifeq+B%`K2^PgAW+J|TK@eLYq6QJ$jDUznN?*qlVLwVZwd;UulagRoFD-U}L zj?Y~r0Ju>69SB!urMUb>%SR2jtH`%1Fu9n?y$VL{0<6!p9*JLXlX>2u2wNaldhYfM zciY5|?^Qx`^3zDmc%QC*#;cOZ#Bv7}aZZ6=PcG%M#Y9)gM68^#y-*Wr5=m^;LGsm{MEFxe!Y(dKa(#fhTVmM_3a4~MS`M7*z*DD>|n%|&&I&%&_ zOJ1VK{fW;$LvAsy$8Xew)R~_>7?*g*zf=$KCmc{7rZ5ZNq?l3?6SwANid>gP>ezUk zvt%(Cdi^VT?ypiXrj%(-70Fw)Dps061J#>fh4uwQ=0%0 z8UJzx+Z1e9Fs|Tgg6y3+Bdi_u*C=jvQbfE&o3>Rgz=U6`1nK*vr+wpztD5WD3YeJ& z-#A9D@WBIj`Zln6C*+^fzmhCwOYX1I?08;t=S5sKyZ6o4O-%l3r7%wPjk=mrP*ZSP z!N(MwRqz=F4=Q*_!N*nHk973~T{U&}69pes@T7uXJuqVQ8C`u-fr+lq>&iY;otyf!`yFa^f|a8*$ILRqkoG+CAmgxF_9O_j@<$e&^P?-@5gM-?$C!HQ{Wf*V?ky_0ys& sq8?Ws&UM_Qzo=4LUFeEBM|Zi=!8k5F;n{+VikG6%o?*oM2TE)H4Ix>{lK=n! literal 48303 zcmcJ&349z^e&5+$eWK9-LGZq$7A1;=M1neIbU>6wJSFM?X_6X^Fw!)es0N5;qZ_Pl zkU|eIqY>phvSTONjWb8ctYfb>m+f4;aV}4?>)hVWwb|Yro;aJ$d>qGd#z}0AMCSYZ zzp8392szGvJ`1v|Ust_)_3FL<```cnYYq(+68!z;+b_=j?1zcOujt3>za9K|o=@v` zCXw(HQwcxmd-dd0(!RZ^lzpeC()OL1%Gh^yDr?`lshoZ1r}FkaFg0M`g{gvl4^9o* zcX6s{-$PSF_B}i`%y+83VSZ$4B&lbn>l^1sr$()usc)L!Jhj=%+4|7@mZ>dP&Qabv zwbjb``nLJ)Q`@aPKzYa14l5TZ@0{9cM`o<_V1zm_|)T8zPJ9V`A<)M+U_mY56wR@^@Nr0qx|I5lUCkae`@~e zsi&=ce|=*9nW<;2`~c-6Q%9`4um0@(b5qY*`9aE`nfi>C_t&4FKRR{P$_FSPn>uFY zG0MlMj$8R5$|t5ySovYfC#OzY`5@&}Q>UzasQ$wIi&HOJdAxpl{-vpxtbCaA%Tq5W z6V?5{HyM<9^GE8h%)dJIs$G4wJ~@A8>I~)G{$usC^WQV|J=8omb$%dGeaL_OMsn&x z^g$|)!he!;PgXy7BQ^Dg|5SCnHdXt)|8(`u znY2IQKl5IC>a789!1KT1AF19~Jy0!Gzpx|WKRe^?Nbvm}=k{{;3)S=P4CkKace(ny ze$Qn6&-l;3mzlcA+479%AN7yXVuj}(_fPQL6F0o6Oa95~7yMIvp00j@J6`Z#q|Wr3 zv#0%+IP3c_PgSc`{}umLf6_nWpY^}TKj)wKFZi$dpY>n&Kj**UPx+ts-}K+|zu=es zi+;twRk0s9~wk4)!tCy>@jLBSe?qBIYW%E~1^+6o%rgq3jLRsq z5%?`yYxwQzc7Ku4x>nuC@w&#D8oQW-H*L)G{@eZ%HJdi)9ehIND&ZO;rdoP}{?5}Wd;D1T;;d`9VUFNJ^ZQn>tE&4aA z_ZU1pUA@<}<-g~DFSW1Pv%k-;QNMP>V_dJ(NBZY~zyAYVe;bIp>HlE$y8lB!&;!I;L zlQDSv1O9*aUYh4E`~SVQVb^(!Kj{Ai?X38JDD3f9qn`7QS^sPP*SY#7-#eceyVaRE z-khJWH2hM1u2C&D7us{pM(bc{t}%CLX0Bc>>C$wwG1IQL+T#mLrG=n5U2V1I8nffy z7FC%XOLhn9!D4y3GJUzqkM!9}`!e5IK(E?pQ!1WXY)q@&3rh>CF?1oQOjj>ermuu2 zQb!w0oX;Px)a#W?b!%n1($c-@6LZu09(bYJs0NjGGvJ4J`i!cdZga*}cV%}q{mPu( znm=PNQ>m-&`RdihYGYbWr_NW^V)jDy?Y1?Pxv;q49vXM>jk71q$6q{o{N>Y=FHkr0 zT7&LW7=E$YyfQbxP=B=&T&b!rcBZvZosQ28ovkm<&NW`GG%B;z;M++CiFyNPgSkfg z#cF+lABERiTss*A_BtE%xg8B1=Y}uX zU6uOl!5r5(lbM-o*!zys$7ay3&s{3dSKAf8(ynkhJyV&}XyndcnOj(>`V@T}a`?edHplX46s=i5s*J0sESo~!EJ z?1l43gW1J-z^t2Hu*RnaldJzU{Lk}g9pF$+z~v;SJUGOnCoi!i?8)PWLW#fQi$Oqt z>Pw}?RyAnZ`dKi5XqDzGOQqK0!U7kYG#0FGr_w52uh#2fE92v(YupXcReaXb#6;=S z61b8VlWgx!kDCSIgyYpEZp6Cvqp1g4K+_~4zUf{>D{g^!ZN|{q<&UFW0JAd?r zli`oi$unhl>+v(MymIn5H++x?j?oP}eE9jpmz(p|!&fS;`9m6v!`GX^l@`EKJ$&lu z$)ks_RU6k11EPl+z{}0Xdy zKCKZB`9#`F=lR&byk}SM0)-qk_18MZp$7_8K%tthW~y0`Yqpx3Nr9qMetIVR{fU{R zpYgNr6#@9m7Q-0!n;)CSmSh|}5f~tXL z*$qZ;k{biP(pajSZdL0uy)z$xcgp4IdZpDWm)*=9SM8TL%=2kwISgmA$wIPo&${n1 z9zAASa2NflEO1j})^R_1GqIegAxP{<)aWoJMv?E-trVk_p6rZ#ab=?P#g)>5nV>mO zVXOzpyi}G|KVNCvYXncxs=dPp$>3#`V~`5;z%IZGjw+Y0>FIidbaHDleW$bMlit8; zj}=|RPpicV9W?C0W*xS0=nlj+RXR%tYt*0EPq*@pE5r8=M1 zD2IH)OM0GndnDz3?4^f4+By*EdN%`h2$q5uDBm@v0)}`@+l=3hWD~Ke4ItEDEN-US z>00Ji@>-HH^*aCHl+lDzOcPoqwLJ$`x_)_X`f_QZ3CSt7n<0jRsDm{j5Uo-(D8&>a zL^-cku0ZWeB4u-xdI|blEio&-dZkO%=?d!@JSE�tM2vYo??HP;$t|;XEx_J1H)r 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 diff --git a/venv/lib/python3.10/site-packages/_pytest/config/__pycache__/argparsing.cpython-310.pyc b/venv/lib/python3.10/site-packages/_pytest/config/__pycache__/argparsing.cpython-310.pyc index 66d2c1224cbda032791247efca8548cbbd5bdfaa..88ca6d4bb3acc0c363762f3a948c49e823d82aac 100644 GIT binary patch literal 19580 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 literal 18268 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 N94mGvUYYvZ{{gi1@l5~# diff --git a/venv/lib/python3.10/site-packages/_pytest/config/__pycache__/compat.cpython-310.pyc b/venv/lib/python3.10/site-packages/_pytest/config/__pycache__/compat.cpython-310.pyc index b5c206d0df4f0f4a8354154824e7afb766200caf..8bfc99bbb60a1bc6a24b64a6ce47fa77af0a39dc 100644 GIT binary patch literal 2856 zcmaJ@&5s+m6(7!wq|vPQ%ih$9?YNURO`0mNAi9A-s2R@%b|nz z&-LG=M@txKa=tY&x$Zcu`Ui+VI>Hewb0)0M7}0L#PQ1QnX)p8pK6rlCm^AxM%WGt< zNze}#>+OCU>s}W2L(vqiY-!Tzcd*tJ0qEuavZdSE%4D^_3OW=^q9c~^Tlrg~zb01Y zx>(zC#1(N#y!gQHU%2Inm&D}zWBRXMc5tJ9p4~e{USIFPLx;UVrDXLD75Tu+fcP`BilI zi!dETyM-P_bZr#pBC0irVkM(tx-auckK`=Mr4*4aqFouuKTYGT>(&Z~AII5L-c+hk zU{SEt570e!)MX4$KIx5$iR_KzYV!89lB#!5s4+}TWbfD8H@AB@f4`TdyFK`HROCP3 zeD|GRm1_AmM8@$@Ry|&OyO$LCARS_uK-0}~W|nv|lF8VHpYEzC6FbahYs`P@KUG&T zP4IuFd6-=%r)`3eP9JXRyMpySu}(RKtnkGJ(H0@7hPWtJ#44z!ScCI6%mwn@_5oaT z8*#g%iqB_<{|X~CJxZ%+B(qYg=s?9~1^*ROc#{GgB{5*GDl0k7b(%$GHXROUQ8g_I zSyePhKSy?vTm{I`VF-D2kY-sViqLLMb(Bn%lDW=iV9F|gT}L%SqQMk`=dOxM=$1;H zTPQjkv9m~f>rB~6Jc~y0zFY(~8O8Y!Vp*(Vf{>-m1vG*h(e89ut8PL%ge#fFvnOk{ z37faU`j9y45?rcYME5Qj;EekHSkx<+SFRO{{v zC=+mw1k;@V6tjQRnISv8^8Ju(p2$6EXKAG=_wFSO;wO#hXH-J=ID5#SyN-&m1}!{P z67>qvO#KMMcXerT4hhZ)M(R~`hnJtLh!ry4Sk8FkkKw>QZ_c)yV`!}1IeW?Z%zflOaiBwiD$L3|9RG?78!m zbL0*$DGcIV(tnYyako~yk*|CprMmncZW`_g4VfL-q`L8u zs28L297UBAz@z6Q8E0}|W;VRtz<6a*ng9u&1N)WniBfHdqc9h05^GykQ~-`Ge_c-z z7)?lb@O>(Urc)QtdiK~e;pto%pAyIf3u$!@+@Z}L?U5JnUEaKIEPDY!n}dqicG1Ee2*xb#!H-^YWFU< z6_pG!G=!lr%-vwYrGPc<{P5cUH*W{F*jn*JI{&l+;VH88Pr!wQ)unqX@g{Lw>N*A! za=!So;HJYlJ#DoehElvpI9ETWjjvO;P2E{lvJ}~*UPeb&+czk1d=>_wx8hlTw|Vxy z+qcm^?&oyfASDJHPfcN0aqr?zORMc@IP7vBqdMxSMMDBq#=W?f-S zvwkV-%LuS0L{(QZL4g+*({+GXIY0NQ2L|?s}h!s72`ta7w785C%Xd SQEtP*|`Wuc~@}+4%j!upSH& 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 F{|E4s`|AJz diff --git a/venv/lib/python3.10/site-packages/_pytest/config/argparsing.py b/venv/lib/python3.10/site-packages/_pytest/config/argparsing.py index d3f0191..8216ad8 100644 --- a/venv/lib/python3.10/site-packages/_pytest/config/argparsing.py +++ b/venv/lib/python3.10/site-packages/_pytest/config/argparsing.py @@ -1,69 +1,83 @@ +# mypy: allow-untyped-defs +from __future__ import annotations + import argparse +from collections.abc import Callable +from collections.abc import Mapping +from collections.abc import Sequence import os import sys -import warnings -from gettext import gettext from typing import Any -from typing import Callable -from typing import cast -from typing import Dict -from typing import List -from typing import Mapping +from typing import final +from typing import Literal from typing import NoReturn -from typing import Optional -from typing import Sequence -from typing import Tuple -from typing import TYPE_CHECKING -from typing import Union +from .exceptions import UsageError import _pytest._io -from _pytest.compat import final -from _pytest.config.exceptions import UsageError -from _pytest.deprecated import ARGUMENT_PERCENT_DEFAULT -from _pytest.deprecated import ARGUMENT_TYPE_STR -from _pytest.deprecated import ARGUMENT_TYPE_STR_CHOICE from _pytest.deprecated import check_ispytest -if TYPE_CHECKING: - from typing_extensions import Literal 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. + """Parser for command line arguments and config-file values. :ivar extra_info: Dict of generic param -> value to display in case there's an error processing the command line arguments. """ - prog: Optional[str] = None - def __init__( self, - usage: Optional[str] = None, - processopt: Optional[Callable[["Argument"], None]] = None, + 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, Optional[str], Any]] = {} - self._ininames: List[str] = [] - self.extra_info: Dict[str, Any] = {} - def processoption(self, option: "Argument") -> None: + from _pytest._argcomplete import filescompleter + + self._processopt = processopt + self.extra_info: dict[str, Any] = {} + self.optparser = PytestArgumentParser(self, usage, self.extra_info) + anonymous_arggroup = self.optparser.add_argument_group("Custom options") + self._anonymous = OptionGroup( + anonymous_arggroup, "_anonymous", self, _ispytest=True + ) + self._groups = [self._anonymous] + file_or_dir_arg = self.optparser.add_argument(FILE_OR_DIR, nargs="*") + file_or_dir_arg.completer = filescompleter # type: ignore + + self._inidict: dict[str, tuple[str, str, Any]] = {} + # Maps alias -> canonical name. + self._ini_aliases: dict[str, str] = {} + + @property + def prog(self) -> str: + return self.optparser.prog + + @prog.setter + def prog(self, value: str) -> None: + self.optparser.prog = value + + def processoption(self, option: Argument) -> None: if self._processopt: if option.dest: self._processopt(option) def getgroup( - self, name: str, description: str = "", after: Optional[str] = None - ) -> "OptionGroup": + self, name: str, description: str = "", after: str | None = None + ) -> OptionGroup: """Get (or create) a named option Group. :param name: Name of the option group. @@ -79,12 +93,17 @@ class Parser: for group in self._groups: if group.name == name: return group - group = OptionGroup(name, description, parser=self, _ispytest=True) + + arggroup = self.optparser.add_argument_group(description or name) + group = OptionGroup(arggroup, name, self, _ispytest=True) i = 0 for i, grp in enumerate(self._groups): if grp.name == after: break self._groups.insert(i + 1, group) + # argparse doesn't provide a way to control `--help` order, so must + # access its internals ☹. + self.optparser._action_groups.insert(i + 1, self.optparser._action_groups.pop()) return group def addoption(self, *opts: str, **attrs: Any) -> None: @@ -93,7 +112,7 @@ class Parser: :param opts: Option names, can be short or long options. :param attrs: - Same attributes as the argparse library's :py:func:`add_argument() + Same attributes as the argparse library's :meth:`add_argument() ` function accepts. After command line parsing, options are available on the pytest config @@ -105,50 +124,32 @@ class Parser: def parse( self, - args: Sequence[Union[str, "os.PathLike[str]"]], - namespace: Optional[argparse.Namespace] = None, + args: Sequence[str | os.PathLike[str]], + namespace: argparse.Namespace | None = None, ) -> argparse.Namespace: + """Parse the arguments. + + Unlike ``parse_known_args`` and ``parse_known_and_unknown_args``, + raises PrintHelp on `--help` and UsageError on unknown flags + + :meta private: + """ 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[Union[str, "os.PathLike[str]"]], - option: argparse.Namespace, - namespace: Optional[argparse.Namespace] = 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)) + if namespace is None: + namespace = argparse.Namespace() + try: + namespace._raise_print_help = True + return self.optparser.parse_intermixed_args(strargs, namespace=namespace) + finally: + del namespace._raise_print_help def parse_known_args( self, - args: Sequence[Union[str, "os.PathLike[str]"]], - namespace: Optional[argparse.Namespace] = None, + args: Sequence[str | os.PathLike[str]], + namespace: argparse.Namespace | None = None, ) -> argparse.Namespace: """Parse the known arguments at this point. @@ -158,35 +159,47 @@ class Parser: def parse_known_and_unknown_args( self, - args: Sequence[Union[str, "os.PathLike[str]"]], - namespace: Optional[argparse.Namespace] = None, - ) -> Tuple[argparse.Namespace, List[str]]: + 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. + remaining unknown flag arguments. :returns: A tuple containing an argparse namespace object for the known - arguments, and a list of the unknown arguments. + arguments, and a list of unknown flag arguments. """ - optparser = self._getparser() strargs = [os.fspath(x) for x in args] - return optparser.parse_known_args(strargs, namespace=namespace) + if sys.version_info < (3, 12, 8) or (3, 13) <= sys.version_info < (3, 13, 1): + # Older argparse have a bugged parse_known_intermixed_args. + namespace, unknown = self.optparser.parse_known_args(strargs, namespace) + assert namespace is not None + file_or_dir = getattr(namespace, FILE_OR_DIR) + unknown_flags: list[str] = [] + for arg in unknown: + (unknown_flags if arg.startswith("-") else file_or_dir).append(arg) + return namespace, unknown_flags + else: + return self.optparser.parse_known_intermixed_args(strargs, namespace) def addini( self, name: str, help: str, - type: Optional[ - "Literal['string', 'paths', 'pathlist', 'args', 'linelist', 'bool']" - ] = None, - default: Any = None, + type: Literal[ + "string", "paths", "pathlist", "args", "linelist", "bool", "int", "float" + ] + | None = None, + default: Any = NOT_SET, + *, + aliases: Sequence[str] = (), ) -> None: - """Register an ini-file option. + """Register a configuration file option. :param name: - Name of the ini-variable. + Name of the configuration. :param type: - Type of the variable. Can be: + Type of the configuration. Can be: * ``string``: a string * ``bool``: a boolean @@ -194,27 +207,90 @@ class Parser: * ``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 + * ``int``: an integer + * ``float``: a floating-point number + + .. versionadded:: 8.4 + + The ``float`` and ``int`` types. + + For ``paths`` and ``pathlist`` types, they are considered relative to the config-file. + In case the execution is happening without a config-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 a config-file. + Defaults to ``string`` if ``None`` or not passed. :param default: - Default value if no ini-file option exists but is queried. + Default value if no config-file option exists but is queried. + :param aliases: + Additional names by which this option can be referenced. + Aliases resolve to the canonical name. - The value of ini-variables can be retrieved via a call to + .. versionadded:: 9.0 + The ``aliases`` parameter. + + The value of configuration keys can be retrieved via a call to :py:func:`config.getini(name) `. """ - assert type in (None, "string", "paths", "pathlist", "args", "linelist", "bool") + assert type in ( + None, + "string", + "paths", + "pathlist", + "args", + "linelist", + "bool", + "int", + "float", + ) + if type is None: + type = "string" + if default is NOT_SET: + default = get_ini_default_for_type(type) + self._inidict[name] = (help, type, default) - self._ininames.append(name) + + for alias in aliases: + if alias in self._inidict: + raise ValueError( + f"alias {alias!r} conflicts with existing configuration option" + ) + if (already := self._ini_aliases.get(alias)) is not None: + raise ValueError(f"{alias!r} is already an alias of {already!r}") + self._ini_aliases[alias] = name + + +def get_ini_default_for_type( + type: Literal[ + "string", "paths", "pathlist", "args", "linelist", "bool", "int", "float" + ], +) -> Any: + """ + Used by addini to get the default value for a given config option type, when + default is not supplied. + """ + if type in ("paths", "pathlist", "args", "linelist"): + return [] + elif type == "bool": + return False + elif type == "int": + return 0 + elif type == "float": + return 0.0 + else: + return "" class ArgumentError(Exception): """Raised if an Argument instance is created with invalid or inconsistent arguments.""" - def __init__(self, msg: str, option: Union["Argument", str]) -> None: + def __init__(self, msg: str, option: Argument | str) -> None: self.msg = msg self.option_id = str(option) @@ -234,46 +310,22 @@ class Argument: https://docs.python.org/3/library/optparse.html#optparse-standard-option-types """ - _typ_map = {"int": int, "string": str, "float": float, "complex": complex} - 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] = [] - if "%default" in (attrs.get("help") or ""): - warnings.warn(ARGUMENT_PERCENT_DEFAULT, stacklevel=3) + self._short_opts: list[str] = [] + self._long_opts: list[str] = [] try: - typ = attrs["type"] + self.type = attrs["type"] except KeyError: pass - else: - # This might raise a keyerror as well, don't want to catch that. - if isinstance(typ, str): - if typ == "choice": - warnings.warn( - ARGUMENT_TYPE_STR_CHOICE.format(typ=typ, names=names), - stacklevel=4, - ) - # argparse expects a type here take it from - # the type of the first element - attrs["type"] = type(attrs["choices"][0]) - else: - warnings.warn( - ARGUMENT_TYPE_STR.format(typ=typ, names=names), stacklevel=4 - ) - attrs["type"] = Argument._typ_map[typ] - # Used in test_parseopt -> test_parse_defaultgetter. - self.type = attrs["type"] - else: - self.type = typ try: # Attribute existence is tested in Config._processopt. self.default = attrs["default"] except KeyError: pass self._set_opt_strings(names) - dest: Optional[str] = attrs.get("dest") + dest: str | None = attrs.get("dest") if dest: self.dest = dest elif self._long_opts: @@ -285,23 +337,16 @@ class Argument: self.dest = "???" # Needed for the error repr. raise ArgumentError("need a long or short option", self) from e - def names(self) -> List[str]: + 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: + for attr in ("default", "dest", "help", self.dest): try: self._attrs[attr] = getattr(self, attr) except AttributeError: pass - if self._attrs.get("help"): - a = self._attrs["help"] - a = a.replace("%default", "%(default)s") - # a = a.replace('%prog', '%(prog)s') - self._attrs["help"] = a return self._attrs def _set_opt_strings(self, opts: Sequence[str]) -> None: @@ -312,29 +357,29 @@ class Argument: for opt in opts: if len(opt) < 2: raise ArgumentError( - "invalid option string %r: " - "must be at least two characters long" % opt, + 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( - "invalid short option string %r: " - "must be of the form -x, (x any non-dash char)" % opt, + 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( - "invalid long option string %r: " - "must start with --, followed by non-dash" % opt, + 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] = [] + args: list[str] = [] if self._short_opts: args += ["_short_opts: " + repr(self._short_opts)] if self._long_opts: @@ -352,16 +397,15 @@ class OptionGroup: def __init__( self, + arggroup: argparse._ArgumentGroup, name: str, - description: str = "", - parser: Optional[Parser] = None, - *, + parser: Parser | None, _ispytest: bool = False, ) -> None: check_ispytest(_ispytest) + self._arggroup = arggroup self.name = name - self.description = description - self.options: List[Argument] = [] + self.options: list[Argument] = [] self.parser = parser def addoption(self, *opts: str, **attrs: Any) -> None: @@ -375,14 +419,14 @@ class OptionGroup: :param opts: Option names, can be short or long options. :param attrs: - Same attributes as the argparse library's :py:func:`add_argument() + 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("option names %s already added" % conflict) + raise ValueError(f"option names {conflict} already added") option = Argument(*opts, **attrs) self._addoption_instance(option, shortupper=False) @@ -390,101 +434,47 @@ class OptionGroup: option = Argument(*opts, **attrs) self._addoption_instance(option, shortupper=True) - def _addoption_instance(self, option: "Argument", shortupper: bool = False) -> None: + 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._arggroup.add_argument(*option.names(), **option.attrs()) self.options.append(option) -class MyOptionParser(argparse.ArgumentParser): +class PytestArgumentParser(argparse.ArgumentParser): def __init__( self, parser: Parser, - extra_info: Optional[Dict[str, Any]] = None, - prog: Optional[str] = None, + usage: str | None, + extra_info: dict[str, str], ) -> None: self._parser = parser super().__init__( - prog=prog, - usage=parser._usage, + usage=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 {} + self.extra_info = extra_info 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"): - # Type ignored because the attribute is set dynamically. - msg = f"{msg} ({self._parser._config_source_hint})" # type: ignore - + if self.extra_info: + msg += "\n" + "\n".join( + f" {k}: {v}" for k, v in sorted(self.extra_info.items()) + ) 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: Optional[Sequence[str]] = None, - namespace: Optional[argparse.Namespace] = 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: %s" % (" ".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[:2] < (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 - ) -> Optional[Tuple[Optional[argparse.Action], str, Optional[str]]]: - if not arg_string: - return None - if not arg_string[0] 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. @@ -504,7 +494,7 @@ class DropShorterLongHelpFormatter(argparse.HelpFormatter): orgstr = super()._format_action_invocation(action) if orgstr and orgstr[0] != "-": # only optional arguments return orgstr - res: Optional[str] = getattr(action, "_formatted_action_invocation", None) + res: str | None = getattr(action, "_formatted_action_invocation", None) if res: return res options = orgstr.split(", ") @@ -513,13 +503,13 @@ class DropShorterLongHelpFormatter(argparse.HelpFormatter): action._formatted_action_invocation = orgstr # type: ignore return orgstr return_list = [] - short_long: Dict[str, str] = {} + short_long: dict[str, str] = {} for option in options: if len(option) == 2 or option[2] == " ": continue if not option.startswith("--"): raise ArgumentError( - 'long optional argument without "--": [%s]' % (option), option + f'long optional argument without "--": [{option}]', option ) xxoption = option[2:] shortened = xxoption.replace("-", "") @@ -549,3 +539,40 @@ class DropShorterLongHelpFormatter(argparse.HelpFormatter): for line in text.splitlines(): lines.extend(textwrap.wrap(line.strip(), width)) return lines + + +class OverrideIniAction(argparse.Action): + """Custom argparse action that makes a CLI flag equivalent to overriding an + option, in addition to behaving like `store_true`. + + This can simplify things since code only needs to inspect the config option + and not consider the CLI flag. + """ + + def __init__( + self, + option_strings: Sequence[str], + dest: str, + nargs: int | str | None = None, + *args, + ini_option: str, + ini_value: str, + **kwargs, + ) -> None: + super().__init__(option_strings, dest, 0, *args, **kwargs) + self.ini_option = ini_option + self.ini_value = ini_value + + def __call__( + self, + parser: argparse.ArgumentParser, + namespace: argparse.Namespace, + *args, + **kwargs, + ) -> None: + setattr(namespace, self.dest, True) + current_overrides = getattr(namespace, "override_ini", None) + if current_overrides is None: + current_overrides = [] + current_overrides.append(f"{self.ini_option}={self.ini_value}") + setattr(namespace, "override_ini", current_overrides) diff --git a/venv/lib/python3.10/site-packages/_pytest/config/compat.py b/venv/lib/python3.10/site-packages/_pytest/config/compat.py index 5bd922a..21eab4c 100644 --- a/venv/lib/python3.10/site-packages/_pytest/config/compat.py +++ b/venv/lib/python3.10/site-packages/_pytest/config/compat.py @@ -1,15 +1,20 @@ +from __future__ import annotations + +from collections.abc import Mapping import functools -import warnings from pathlib import Path -from typing import Optional +from typing import Any +import warnings + +import pluggy from ..compat import LEGACY_PATH from ..compat import legacy_path from ..deprecated import HOOK_LEGACY_PATH_ARG -from _pytest.nodes import _check_path + # hookname: (Path, LEGACY_PATH) -imply_paths_hooks = { +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"), @@ -18,6 +23,14 @@ imply_paths_hooks = { } +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 @@ -27,24 +40,24 @@ class PathAwareHookProxy: this may have to be changed later depending on bugs """ - def __init__(self, hook_caller): - self.__hook_caller = hook_caller + def __init__(self, hook_relay: pluggy.HookRelay) -> None: + self._hook_relay = hook_relay - def __dir__(self): - return dir(self.__hook_caller) + def __dir__(self) -> list[str]: + return dir(self._hook_relay) - def __getattr__(self, key, _wraps=functools.wraps): - hook = getattr(self.__hook_caller, key) + 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] - @_wraps(hook) - def fixed_hook(**kw): - path_value: Optional[Path] = kw.pop(path_var, None) - fspath_value: Optional[LEGACY_PATH] = kw.pop(fspath_var, None) + @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( @@ -65,6 +78,8 @@ class PathAwareHookProxy: 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 + return fixed_hook # type: ignore[return-value] diff --git a/venv/lib/python3.10/site-packages/_pytest/config/exceptions.py b/venv/lib/python3.10/site-packages/_pytest/config/exceptions.py index 4f1320e..d84a9ea 100644 --- a/venv/lib/python3.10/site-packages/_pytest/config/exceptions.py +++ b/venv/lib/python3.10/site-packages/_pytest/config/exceptions.py @@ -1,10 +1,14 @@ -from _pytest.compat import final +from __future__ import annotations + +from typing import final @final class UsageError(Exception): """Error in pytest usage or invocation.""" + __module__ = "pytest" + class PrintHelp(Exception): """Raised when pytest should print its help to skip the rest of the diff --git a/venv/lib/python3.10/site-packages/_pytest/config/findpaths.py b/venv/lib/python3.10/site-packages/_pytest/config/findpaths.py index 02674ff..3c628a0 100644 --- a/venv/lib/python3.10/site-packages/_pytest/config/findpaths.py +++ b/venv/lib/python3.10/site-packages/_pytest/config/findpaths.py @@ -1,14 +1,14 @@ +from __future__ import annotations + +from collections.abc import Iterable +from collections.abc import Sequence +from dataclasses import dataclass +from dataclasses import KW_ONLY import os -import sys from pathlib import Path -from typing import Dict -from typing import Iterable -from typing import List -from typing import Optional -from typing import Sequence -from typing import Tuple -from typing import TYPE_CHECKING -from typing import Union +import sys +from typing import Literal +from typing import TypeAlias import iniconfig @@ -18,8 +18,29 @@ from _pytest.pathlib import absolutepath from _pytest.pathlib import commonpath from _pytest.pathlib import safe_exists -if TYPE_CHECKING: - from . import Config + +@dataclass(frozen=True) +class ConfigValue: + """Represents a configuration value with its origin and parsing mode. + + This allows tracking whether a value came from a configuration file + or from a CLI override (--override-ini), which is important for + determining precedence when dealing with ini option aliases. + + The mode tracks the parsing mode/data model used for the value: + - "ini": from INI files or [tool.pytest.ini_options], where the only + supported value types are `str` or `list[str]`. + - "toml": from TOML files (not in INI mode), where native TOML types + are preserved. + """ + + value: object + _: KW_ONLY + origin: Literal["file", "override"] + mode: Literal["ini", "toml"] + + +ConfigDict: TypeAlias = dict[str, ConfigValue] def _parse_ini_config(path: Path) -> iniconfig.IniConfig: @@ -36,21 +57,23 @@ def _parse_ini_config(path: Path) -> iniconfig.IniConfig: def load_config_dict_from_file( filepath: Path, -) -> Optional[Dict[str, Union[str, List[str]]]]: +) -> ConfigDict | 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()) + return { + k: ConfigValue(v, origin="file", mode="ini") + for k, v in iniconfig["pytest"].items() + } else: # "pytest.ini" files are always the source of configuration, even if empty. - if filepath.name == "pytest.ini": + if filepath.name in {"pytest.ini", ".pytest.ini"}: return {} # '.cfg' files are considered if they contain a "[tool:pytest]" section. @@ -58,13 +81,18 @@ def load_config_dict_from_file( iniconfig = _parse_ini_config(filepath) if "tool:pytest" in iniconfig.sections: - return dict(iniconfig["tool:pytest"].items()) + return { + k: ConfigValue(v, origin="file", mode="ini") + for k, v in 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. + # '.toml' files are considered if they contain a [tool.pytest] table (toml mode) + # or [tool.pytest.ini_options] table (ini mode) for pyproject.toml, + # or [pytest] table (toml mode) for pytest.toml/.pytest.toml. elif filepath.suffix == ".toml": if sys.version_info >= (3, 11): import tomllib @@ -77,25 +105,67 @@ def load_config_dict_from_file( 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) -> Union[str, List[str]]: - return v if isinstance(v, list) else str(v) + # pytest.toml and .pytest.toml use [pytest] table directly. + if filepath.name in ("pytest.toml", ".pytest.toml"): + pytest_config = config.get("pytest", {}) + if pytest_config: + # TOML mode - preserve native TOML types. + return { + k: ConfigValue(v, origin="file", mode="toml") + for k, v in pytest_config.items() + } + # "pytest.toml" files are always the source of configuration, even if empty. + return {} - return {k: make_scalar(v) for k, v in result.items()} + # pyproject.toml uses [tool.pytest] or [tool.pytest.ini_options]. + else: + tool_pytest = config.get("tool", {}).get("pytest", {}) + + # Check for toml mode config: [tool.pytest] with content outside of ini_options. + toml_config = {k: v for k, v in tool_pytest.items() if k != "ini_options"} + # Check for ini mode config: [tool.pytest.ini_options]. + ini_config = tool_pytest.get("ini_options", None) + + if toml_config and ini_config: + raise UsageError( + f"{filepath}: Cannot use both [tool.pytest] (native TOML types) and " + "[tool.pytest.ini_options] (string-based INI format) simultaneously. " + "Please use [tool.pytest] with native TOML types (recommended) " + "or [tool.pytest.ini_options] for backwards compatibility." + ) + + if toml_config: + # TOML mode - preserve native TOML types. + return { + k: ConfigValue(v, origin="file", mode="toml") + for k, v in toml_config.items() + } + + elif ini_config is not None: + # INI mode - TOML supports richer data types than INI files, but we need to + # convert all scalar values to str for compatibility with the INI system. + def make_scalar(v: object) -> str | list[str]: + return v if isinstance(v, list) else str(v) + + return { + k: ConfigValue(make_scalar(v), origin="file", mode="ini") + for k, v in ini_config.items() + } return None def locate_config( + invocation_dir: Path, args: Iterable[Path], -) -> Tuple[Optional[Path], Optional[Path], Dict[str, Union[str, List[str]]]]: +) -> tuple[Path | None, Path | None, ConfigDict, Sequence[str]]: """Search in the list of arguments for a valid ini-file for pytest, - and return a tuple of (rootdir, inifile, cfg-dict).""" + and return a tuple of (rootdir, inifile, cfg-dict, ignored-config-files), where + ignored-config-files is a list of config basenames found that contain + pytest configuration but were ignored.""" config_names = [ + "pytest.toml", + ".pytest.toml", "pytest.ini", ".pytest.ini", "pyproject.toml", @@ -104,21 +174,39 @@ def locate_config( ] args = [x for x in args if not str(x).startswith("-")] if not args: - args = [Path.cwd()] + args = [invocation_dir] + found_pyproject_toml: Path | None = None + ignored_config_files: list[str] = [] + 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 - return None, None, {} + index = config_names.index(config_name) + for remainder in config_names[index + 1 :]: + p2 = base / remainder + if ( + p2.is_file() + and load_config_dict_from_file(p2) is not None + ): + ignored_config_files.append(remainder) + return base, p, ini_config, ignored_config_files + if found_pyproject_toml is not None: + return found_pyproject_toml.parent, found_pyproject_toml, {}, [] + return None, None, {}, [] -def get_common_ancestor(paths: Iterable[Path]) -> Path: - common_ancestor: Optional[Path] = 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 @@ -134,13 +222,13 @@ def get_common_ancestor(paths: Iterable[Path]) -> Path: if shared is not None: common_ancestor = shared if common_ancestor is None: - common_ancestor = Path.cwd() + 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 get_dirs_from_args(args: Iterable[str]) -> list[Path]: def is_option(x: str) -> bool: return x.startswith("-") @@ -162,26 +250,70 @@ def get_dirs_from_args(args: Iterable[str]) -> List[Path]: return [get_dir_from_path(path) for path in possible_paths if safe_exists(path)] +def parse_override_ini(override_ini: Sequence[str] | None) -> ConfigDict: + """Parse the -o/--override-ini command line arguments and return the overrides. + + :raises UsageError: + If one of the values is malformed. + """ + overrides = {} + # 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 override_ini or (): + 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: + overrides[key] = ConfigValue(user_ini_value, origin="override", mode="ini") + return overrides + + CFG_PYTEST_SECTION = "[pytest] section in {filename} files is no longer supported, change to [tool:pytest] instead." def determine_setup( - inifile: Optional[str], + *, + inifile: str | None, + override_ini: Sequence[str] | None, args: Sequence[str], - rootdir_cmd_arg: Optional[str] = None, - config: Optional["Config"] = None, -) -> Tuple[Path, Optional[Path], Dict[str, Union[str, List[str]]]]: + rootdir_cmd_arg: str | None, + invocation_dir: Path, +) -> tuple[Path, Path | None, ConfigDict, Sequence[str]]: + """Determine the rootdir, inifile and ini configuration values from the + command line arguments. + + :param inifile: + The `--inifile` command line argument, if given. + :param override_ini: + The -o/--override-ini command line arguments, 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. + + :raises UsageError: + """ rootdir = None dirs = get_dirs_from_args(args) + ignored_config_files: Sequence[str] = [] + if inifile: inipath_ = absolutepath(inifile) - inipath: Optional[Path] = inipath_ + 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(dirs) - rootdir, inipath, inicfg = locate_config([ancestor]) + ancestor = get_common_ancestor(invocation_dir, dirs) + rootdir, inipath, inicfg, ignored_config_files = 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(): @@ -189,25 +321,25 @@ def determine_setup( break else: if dirs != [ancestor]: - rootdir, inipath, inicfg = locate_config(dirs) + rootdir, inipath, inicfg, _ = locate_config(invocation_dir, dirs) if rootdir is None: - if config is not None: - cwd = config.invocation_params.dir - else: - cwd = Path.cwd() - rootdir = get_common_ancestor([cwd, ancestor]) + 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( - "Directory '{}' not found. Check your '--rootdir' option.".format( - rootdir - ) + f"Directory '{rootdir}' not found. Check your '--rootdir' option." ) + + ini_overrides = parse_override_ini(override_ini) + inicfg.update(ini_overrides) + assert rootdir is not None - return rootdir, inipath, inicfg or {} + return rootdir, inipath, inicfg, ignored_config_files def is_fs_root(p: Path) -> bool: diff --git a/venv/lib/python3.10/site-packages/_pytest/debugging.py b/venv/lib/python3.10/site-packages/_pytest/debugging.py index a3f8080..de1b268 100644 --- a/venv/lib/python3.10/site-packages/_pytest/debugging.py +++ b/venv/lib/python3.10/site-packages/_pytest/debugging.py @@ -1,21 +1,21 @@ +# mypy: allow-untyped-defs +# ruff: noqa: T100 """Interactive debugging with PDB, the Python Debugger.""" + +from __future__ import annotations + import argparse +from collections.abc import Callable +from collections.abc import Generator import functools import sys import types -import unittest from typing import Any -from typing import Callable -from typing import Generator -from typing import List -from typing import Optional -from typing import Tuple -from typing import Type -from typing import TYPE_CHECKING -from typing import Union +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 @@ -24,13 +24,10 @@ from _pytest.config.argparsing import Parser from _pytest.config.exceptions import UsageError from _pytest.nodes import Node from _pytest.reports import BaseReport - -if TYPE_CHECKING: - from _pytest.capture import CaptureManager - from _pytest.runner import CallInfo +from _pytest.runner import CallInfo -def _validate_usepdb_cls(value: str) -> Tuple[str, str]: +def _validate_usepdb_cls(value: str) -> tuple[str, str]: """Validate syntax of --pdbcls option.""" try: modname, classname = value.split(":") @@ -43,13 +40,13 @@ def _validate_usepdb_cls(value: str) -> Tuple[str, str]: def pytest_addoption(parser: Parser) -> None: group = parser.getgroup("general") - group._addoption( + group.addoption( "--pdb", dest="usepdb", action="store_true", help="Start the interactive Python debugger on errors or KeyboardInterrupt", ) - group._addoption( + group.addoption( "--pdbcls", dest="usepdb_cls", metavar="modulename:classname", @@ -57,7 +54,7 @@ def pytest_addoption(parser: Parser) -> None: help="Specify a custom interactive Python debugger for use with --pdb." "For example: --pdbcls=IPython.terminal.debugger:TerminalPdb", ) - group._addoption( + group.addoption( "--trace", dest="trace", action="store_true", @@ -95,22 +92,22 @@ def pytest_configure(config: Config) -> None: class pytestPDB: """Pseudo PDB that defers to the real pdb.""" - _pluginmanager: Optional[PytestPluginManager] = None - _config: Optional[Config] = None - _saved: List[ - Tuple[Callable[..., None], Optional[PytestPluginManager], Optional[Config]] + _pluginmanager: PytestPluginManager | None = None + _config: Config | None = None + _saved: list[ + tuple[Callable[..., None], PytestPluginManager | None, Config | None] ] = [] _recursive_debug = 0 - _wrapped_pdb_cls: Optional[Tuple[Type[Any], Type[Any]]] = None + _wrapped_pdb_cls: tuple[type[Any], type[Any]] | None = None @classmethod - def _is_capturing(cls, capman: Optional["CaptureManager"]) -> Union[str, bool]: + def _is_capturing(cls, capman: CaptureManager | None) -> str | bool: if capman: return capman.is_capturing() return False @classmethod - def _import_pdb_cls(cls, capman: Optional["CaptureManager"]): + def _import_pdb_cls(cls, capman: CaptureManager | None): if not cls._config: import pdb @@ -149,12 +146,10 @@ class pytestPDB: return wrapped_cls @classmethod - def _get_pdb_wrapper_class(cls, pdb_cls, capman: Optional["CaptureManager"]): + def _get_pdb_wrapper_class(cls, pdb_cls, capman: CaptureManager | None): import _pytest.config - # Type ignored because mypy doesn't support "dynamic" - # inheritance like this. - class PytestPdbWrapper(pdb_cls): # type: ignore[valid-type,misc] + class PytestPdbWrapper(pdb_cls): _pytest_capman = capman _continued = False @@ -164,6 +159,9 @@ class pytestPDB: cls._recursive_debug -= 1 return ret + if hasattr(pdb_cls, "do_debug"): + do_debug.__doc__ = pdb_cls.do_debug.__doc__ + def do_continue(self, arg): ret = super().do_continue(arg) if cls._recursive_debug == 0: @@ -179,8 +177,7 @@ class pytestPDB: else: tw.sep( ">", - "PDB continue (IO-capturing resumed for %s)" - % capturing, + f"PDB continue (IO-capturing resumed for {capturing})", ) assert capman is not None capman.resume() @@ -191,15 +188,17 @@ class pytestPDB: self._continued = True return ret + if hasattr(pdb_cls, "do_continue"): + do_continue.__doc__ = pdb_cls.do_continue.__doc__ + 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. - """ + # 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: @@ -207,6 +206,9 @@ class pytestPDB: return ret + if hasattr(pdb_cls, "do_quit"): + do_quit.__doc__ = pdb_cls.do_quit.__doc__ + do_q = do_quit do_exit = do_quit @@ -241,7 +243,7 @@ class pytestPDB: import _pytest.config if cls._pluginmanager is None: - capman: Optional[CaptureManager] = None + capman: CaptureManager | None = None else: capman = cls._pluginmanager.getplugin("capturemanager") if capman: @@ -263,8 +265,7 @@ class pytestPDB: elif capturing: tw.sep( ">", - "PDB %s (IO-capturing turned off for %s)" - % (method, capturing), + f"PDB {method} (IO-capturing turned off for {capturing})", ) else: tw.sep(">", f"PDB {method}") @@ -285,7 +286,7 @@ class pytestPDB: class PdbInvoke: def pytest_exception_interact( - self, node: Node, call: "CallInfo[Any]", report: BaseReport + self, node: Node, call: CallInfo[Any], report: BaseReport ) -> None: capman = node.config.pluginmanager.getplugin("capturemanager") if capman: @@ -299,18 +300,18 @@ class PdbInvoke: _enter_pdb(node, call.excinfo, report) def pytest_internalerror(self, excinfo: ExceptionInfo[BaseException]) -> None: - tb = _postmortem_traceback(excinfo) - post_mortem(tb) + exc_or_tb = _postmortem_exc_or_tb(excinfo) + post_mortem(exc_or_tb) class PdbTrace: - @hookimpl(hookwrapper=True) - def pytest_pyfunc_call(self, pyfuncitem) -> Generator[None, None, None]: + @hookimpl(wrapper=True) + def pytest_pyfunc_call(self, pyfuncitem) -> Generator[None, object, object]: wrap_pytest_function_for_tracing(pyfuncitem) - yield + return (yield) -def wrap_pytest_function_for_tracing(pyfuncitem): +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 @@ -322,14 +323,14 @@ def wrap_pytest_function_for_tracing(pyfuncitem): # 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): + 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): +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"): @@ -339,7 +340,7 @@ def maybe_wrap_pytest_function_for_tracing(pyfuncitem): def _enter_pdb( node: Node, excinfo: ExceptionInfo[BaseException], rep: BaseReport ) -> BaseReport: - # XXX we re-use the TerminalReporter's terminalwriter + # 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 @@ -361,31 +362,46 @@ def _enter_pdb( tw.sep(">", "traceback") rep.toterminal(tw) tw.sep(">", "entering PDB") - tb = _postmortem_traceback(excinfo) + tb_or_exc = _postmortem_exc_or_tb(excinfo) rep._pdbshown = True # type: ignore[attr-defined] - post_mortem(tb) + post_mortem(tb_or_exc) return rep -def _postmortem_traceback(excinfo: ExceptionInfo[BaseException]) -> types.TracebackType: +def _postmortem_exc_or_tb( + excinfo: ExceptionInfo[BaseException], +) -> types.TracebackType | BaseException: from doctest import UnexpectedException + get_exc = sys.version_info >= (3, 13) 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] + underlying_exc = excinfo.value + if get_exc: + return underlying_exc.exc_info[1] + + return underlying_exc.exc_info[2] elif isinstance(excinfo.value, ConftestImportFailure): # A config.ConftestImportFailure is not useful for post_mortem. # Use the underlying exception instead: - return excinfo.value.excinfo[2] + cause = excinfo.value.cause + if get_exc: + return cause + + assert cause.__traceback__ is not None + return cause.__traceback__ else: assert excinfo._excinfo is not None + if get_exc: + return excinfo._excinfo[1] + return excinfo._excinfo[2] -def post_mortem(t: types.TracebackType) -> None: +def post_mortem(tb_or_exc: types.TracebackType | BaseException) -> None: p = pytestPDB._init_pdb("post_mortem") p.reset() - p.interaction(None, t) + p.interaction(None, tb_or_exc) if p.quitting: outcomes.exit("Quitting debugger") diff --git a/venv/lib/python3.10/site-packages/_pytest/deprecated.py b/venv/lib/python3.10/site-packages/_pytest/deprecated.py index b9c10df..cb5d2e9 100644 --- a/venv/lib/python3.10/site-packages/_pytest/deprecated.py +++ b/venv/lib/python3.10/site-packages/_pytest/deprecated.py @@ -8,111 +8,52 @@ 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 PytestRemovedIn8Warning +from _pytest.warning_types import PytestRemovedIn9Warning +from _pytest.warning_types import PytestRemovedIn10Warning 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", + "pytest_subtests", } -NOSE_SUPPORT = UnformattedWarning( - PytestRemovedIn8Warning, - "Support for nose tests is deprecated and will be removed in a future release.\n" - "{nodeid} is using nose method: `{method}` ({stage})\n" - "See docs: https://docs.pytest.org/en/stable/deprecations.html#support-for-tests-written-for-nose", -) -NOSE_SUPPORT_METHOD = UnformattedWarning( - PytestRemovedIn8Warning, - "Support for nose tests is deprecated and will be removed in a future release.\n" - "{nodeid} is using nose-specific method: `{method}(self)`\n" - "To remove this warning, rename it to `{method}_method(self)`\n" - "See docs: https://docs.pytest.org/en/stable/deprecations.html#support-for-tests-written-for-nose", -) - - -# 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". +# This could have been removed pytest 8, but it's harmless and common, so no rush to remove. YIELD_FIXTURE = PytestDeprecationWarning( "@pytest.yield_fixture is deprecated.\n" "Use @pytest.fixture instead; they are the same." ) -WARNING_CMDLINE_PREPARSE_HOOK = PytestRemovedIn8Warning( - "The pytest_cmdline_preparse hook is deprecated and will be removed in a future release. \n" - "Please use pytest_load_initial_conftests hook instead." -) - -FSCOLLECTOR_GETHOOKPROXY_ISINITPATH = PytestRemovedIn8Warning( - "The gethookproxy() and isinitpath() methods of FSCollector and Package are deprecated; " - "use self.session.gethookproxy() and self.session.isinitpath() instead. " -) - -STRICT_OPTION = PytestRemovedIn8Warning( - "The --strict option is deprecated, use --strict-markers instead." -) - # This deprecation is never really meant to be removed. PRIVATE = PytestDeprecationWarning("A private pytest class or function was used.") -ARGUMENT_PERCENT_DEFAULT = PytestRemovedIn8Warning( - 'pytest now uses argparse. "%default" should be changed to "%(default)s"', -) - -ARGUMENT_TYPE_STR_CHOICE = UnformattedWarning( - PytestRemovedIn8Warning, - "`type` argument to addoption() is the string {typ!r}." - " For choices this is optional and can be omitted, " - " but when supplied should be a type (for example `str` or `int`)." - " (options: {names})", -) - -ARGUMENT_TYPE_STR = UnformattedWarning( - PytestRemovedIn8Warning, - "`type` argument to addoption() is the string {typ!r}, " - " but when supplied should be a type (for example `str` or `int`)." - " (options: {names})", -) - HOOK_LEGACY_PATH_ARG = UnformattedWarning( - PytestRemovedIn8Warning, + 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( - PytestRemovedIn8Warning, + 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", ) -WARNS_NONE_ARG = PytestRemovedIn8Warning( - "Passing None has been deprecated.\n" - "See https://docs.pytest.org/en/latest/how-to/capture-warnings.html" - "#additional-use-cases-of-warnings-in-tests" - " for alternatives in common use cases." -) - -KEYWORD_MSG_ARG = UnformattedWarning( - PytestRemovedIn8Warning, - "pytest.{func}(msg=...) is now deprecated, use pytest.{func}(reason=...) instead", -) - -INSTANCE_COLLECTOR = PytestRemovedIn8Warning( - "The pytest.Instance collector type is deprecated and is no longer used. " - "See https://docs.pytest.org/en/latest/deprecations.html#the-pytest-instance-collector", -) HOOK_LEGACY_MARKING = UnformattedWarning( PytestDeprecationWarning, "The hook{type} {fullname} uses old-style configuration options (marks or attributes).\n" @@ -122,6 +63,18 @@ HOOK_LEGACY_MARKING = UnformattedWarning( "#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" +) + +MONKEYPATCH_LEGACY_NAMESPACE_PACKAGES = PytestRemovedIn10Warning( + "monkeypatch.syspath_prepend() called with pkg_resources legacy namespace packages detected.\n" + "Legacy namespace packages (using pkg_resources.declare_namespace) are deprecated.\n" + "Please use native namespace packages (PEP 420) instead.\n" + "See https://docs.pytest.org/en/stable/deprecations.html#monkeypatch-fixup-namespace-packages" +) + # You want to make some `__init__` or function "private". # # def my_private_function(some, args): diff --git a/venv/lib/python3.10/site-packages/_pytest/doctest.py b/venv/lib/python3.10/site-packages/_pytest/doctest.py index ca41a98..cd255f5 100644 --- a/venv/lib/python3.10/site-packages/_pytest/doctest.py +++ b/venv/lib/python3.10/site-packages/_pytest/doctest.py @@ -1,28 +1,26 @@ +# mypy: allow-untyped-defs """Discover and run doctests in modules and test files.""" + +from __future__ import annotations + import bdb +from collections.abc import Callable +from collections.abc import Generator +from collections.abc import Iterable +from collections.abc import Sequence +from contextlib import contextmanager import functools import inspect import os +from pathlib import Path import platform +import re import sys import traceback import types -import warnings -from contextlib import contextmanager -from pathlib import Path from typing import Any -from typing import Callable -from typing import Dict -from typing import Generator -from typing import Iterable -from typing import List -from typing import Optional -from typing import Pattern -from typing import Sequence -from typing import Tuple -from typing import Type from typing import TYPE_CHECKING -from typing import Union +import warnings from _pytest import outcomes from _pytest._code.code import ExceptionInfo @@ -33,20 +31,22 @@ 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 FixtureRequest +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.pathlib import import_path 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" @@ -64,7 +64,7 @@ DOCTEST_REPORT_CHOICES = ( # Lazy definition of runner class RUNNER_CLASS = None # Lazy definition of output checker class -CHECKER_CLASS: Optional[Type["doctest.OutputChecker"]] = None +CHECKER_CLASS: type[doctest.OutputChecker] | None = None def pytest_addoption(parser: Parser) -> None: @@ -105,7 +105,7 @@ def pytest_addoption(parser: Parser) -> None: "--doctest-ignore-import-errors", action="store_true", default=False, - help="Ignore doctest ImportErrors", + help="Ignore doctest collection errors", dest="doctest_ignore_import_errors", ) group.addoption( @@ -126,17 +126,15 @@ def pytest_unconfigure() -> None: def pytest_collect_file( file_path: Path, parent: Collector, -) -> Optional[Union["DoctestModule", "DoctestTextfile"]]: +) -> 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)) ): - mod: DoctestModule = DoctestModule.from_parent(parent, path=file_path) - return mod + return DoctestModule.from_parent(parent, path=file_path) elif _is_doctest(config, file_path, parent): - txt: DoctestTextfile = DoctestTextfile.from_parent(parent, path=file_path) - return txt + return DoctestTextfile.from_parent(parent, path=file_path) return None @@ -160,7 +158,7 @@ def _is_main_py(path: Path) -> bool: class ReprFailDoctest(TerminalRepr): def __init__( - self, reprlocation_lines: Sequence[Tuple[ReprFileLocation, Sequence[str]]] + self, reprlocation_lines: Sequence[tuple[ReprFileLocation, Sequence[str]]] ) -> None: self.reprlocation_lines = reprlocation_lines @@ -172,12 +170,12 @@ class ReprFailDoctest(TerminalRepr): class MultipleDoctestFailures(Exception): - def __init__(self, failures: Sequence["doctest.DocTestFailure"]) -> None: + def __init__(self, failures: Sequence[doctest.DocTestFailure]) -> None: super().__init__() self.failures = failures -def _init_runner_class() -> Type["doctest.DocTestRunner"]: +def _init_runner_class() -> type[doctest.DocTestRunner]: import doctest class PytestDoctestRunner(doctest.DebugRunner): @@ -189,8 +187,8 @@ def _init_runner_class() -> Type["doctest.DocTestRunner"]: def __init__( self, - checker: Optional["doctest.OutputChecker"] = None, - verbose: Optional[bool] = None, + checker: doctest.OutputChecker | None = None, + verbose: bool | None = None, optionflags: int = 0, continue_on_failure: bool = True, ) -> None: @@ -200,8 +198,8 @@ def _init_runner_class() -> Type["doctest.DocTestRunner"]: def report_failure( self, out, - test: "doctest.DocTest", - example: "doctest.Example", + test: doctest.DocTest, + example: doctest.Example, got: str, ) -> None: failure = doctest.DocTestFailure(test, example, got) @@ -213,9 +211,9 @@ def _init_runner_class() -> Type["doctest.DocTestRunner"]: def report_unexpected_exception( self, out, - test: "doctest.DocTest", - example: "doctest.Example", - exc_info: Tuple[Type[BaseException], BaseException, types.TracebackType], + 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] @@ -231,11 +229,11 @@ def _init_runner_class() -> Type["doctest.DocTestRunner"]: def _get_runner( - checker: Optional["doctest.OutputChecker"] = None, - verbose: Optional[bool] = None, + checker: doctest.OutputChecker | None = None, + verbose: bool | None = None, optionflags: int = 0, continue_on_failure: bool = True, -) -> "doctest.DocTestRunner": +) -> doctest.DocTestRunner: # We need this in order to do a lazy import on doctest global RUNNER_CLASS if RUNNER_CLASS is None: @@ -254,45 +252,50 @@ class DoctestItem(Item): def __init__( self, name: str, - parent: "Union[DoctestTextfile, DoctestModule]", - runner: Optional["doctest.DocTestRunner"] = None, - dtest: Optional["doctest.DocTest"] = None, + 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 - self.fixture_request: Optional[FixtureRequest] = 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 + def from_parent( # type: ignore[override] cls, - parent: "Union[DoctestTextfile, DoctestModule]", + parent: DoctestTextfile | DoctestModule, *, name: str, - runner: "doctest.DocTestRunner", - dtest: "doctest.DocTest", - ): + 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: - if self.dtest is not None: - self.fixture_request = _setup_fixtures(self) - globs = dict(getfixture=self.fixture_request.getfixturevalue) - for name, value in self.fixture_request.getfixturevalue( - "doctest_namespace" - ).items(): - globs[name] = value - self.dtest.globs.update(globs) + 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: - assert self.dtest is not None - assert self.runner is not None _check_all_skipped(self.dtest) self._disable_output_capturing_for_darwin() - failures: List["doctest.DocTestFailure"] = [] + 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] @@ -314,14 +317,14 @@ class DoctestItem(Item): def repr_failure( # type: ignore[override] self, excinfo: ExceptionInfo[BaseException], - ) -> Union[str, TerminalRepr]: + ) -> str | TerminalRepr: import doctest - failures: Optional[ - Sequence[Union[doctest.DocTestFailure, doctest.UnexpectedException]] - ] = None + failures: ( + Sequence[doctest.DocTestFailure | doctest.UnexpectedException] | None + ) = None if isinstance( - excinfo.value, (doctest.DocTestFailure, doctest.UnexpectedException) + excinfo.value, doctest.DocTestFailure | doctest.UnexpectedException ): failures = [excinfo.value] elif isinstance(excinfo.value, MultipleDoctestFailures): @@ -350,7 +353,7 @@ class DoctestItem(Item): # 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) + f"{i + test.lineno + 1:03d} {x}" for (i, x) in enumerate(lines) ] # trim docstring error lines to 10 lines = lines[max(example.lineno - 9, 0) : example.lineno + 1] @@ -368,19 +371,18 @@ class DoctestItem(Item): ).split("\n") else: inner_excinfo = ExceptionInfo.from_exc_info(failure.exc_info) - lines += ["UNEXPECTED EXCEPTION: %s" % repr(inner_excinfo.value)] + 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[Union["os.PathLike[str]", str], Optional[int], str]: - assert self.dtest is not None - return self.path, self.dtest.lineno, "[doctest] %s" % self.name + 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]: +def _get_flag_lookup() -> dict[str, int]: import doctest return dict( @@ -396,8 +398,8 @@ def _get_flag_lookup() -> Dict[str, int]: ) -def get_optionflags(parent): - optionflags_str = parent.config.getini("doctest_optionflags") +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: @@ -405,8 +407,8 @@ def get_optionflags(parent): return flag_acc -def _get_continue_on_failure(config): - continue_on_failure = config.getvalue("doctest_continue_on_failure") +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. @@ -429,7 +431,7 @@ class DoctestTextfile(Module): name = self.path.name globs = {"__name__": "__main__"} - optionflags = get_optionflags(self) + optionflags = get_optionflags(self.config) runner = _get_runner( verbose=False, @@ -446,7 +448,7 @@ class DoctestTextfile(Module): ) -def _check_all_skipped(test: "doctest.DocTest") -> None: +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 @@ -466,13 +468,13 @@ def _is_mocked(obj: object) -> bool: @contextmanager -def _patch_unwrap_mock_aware() -> Generator[None, None, None]: +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: Optional[Callable[[Any], Any]] = None + func: Callable[..., Any], *, stop: Callable[[Any], Any] | None = None ) -> Any: try: if stop is None or stop is _is_mocked: @@ -481,9 +483,9 @@ def _patch_unwrap_mock_aware() -> Generator[None, None, None]: return real_unwrap(func, stop=lambda obj: _is_mocked(obj) or _stop(func)) except Exception as e: warnings.warn( - "Got %r when unwrapping %r. This is usually caused " + 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" % (e, func), + "https://github.com/pytest-dev/pytest/issues/5080", PytestWarning, ) raise @@ -500,41 +502,32 @@ class DoctestModule(Module): import doctest class MockAwareDocTestFinder(doctest.DocTestFinder): - """A hackish doctest finder that overrides stdlib internals to fix a stdlib bug. + 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: - https://github.com/pytest-dev/pytest/issues/3456 - https://bugs.python.org/issue25532 - """ + 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 - def _find_lineno(self, obj, source_lines): - """Doctest code does not take into account `@property`, this - is a hackish way to fix it. https://bugs.python.org/issue17446 + Moreover, wrapped Doctests need to be unwrapped so the correct + line number is returned. #8796 + """ + if isinstance(obj, property): + obj = getattr(obj, "fget", obj) - Wrapped Doctests will need to be unwrapped so the correct - line number is returned. This will be reported upstream. #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) - 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, - ) - - def _find( - self, tests, obj, name, module, source_lines, globs, seen - ) -> None: - if _is_mocked(obj): - return - 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 + return super()._find_lineno( # type:ignore[misc] + obj, + source_lines, ) if sys.version_info < (3, 13): @@ -545,38 +538,27 @@ class DoctestModule(Module): Here we override `_from_module` to check the underlying function instead. https://github.com/python/cpython/issues/107995 """ - if hasattr(functools, "cached_property") and isinstance( - object, functools.cached_property - ): + 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] - else: # pragma: no cover - pass + 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) - if self.path.name == "conftest.py": - module = self.config.pluginmanager._importconftest( - self.path, - self.config.getoption("importmode"), - rootpath=self.config.rootpath, - ) - else: - try: - module = import_path( - self.path, - root=self.config.rootpath, - mode=self.config.getoption("importmode"), - ) - except ImportError: - if self.config.getvalue("doctest_ignore_import_errors"): - skip("unable to import module %r" % self.path) - else: - raise # Uses internal doctest module parsing mechanism. finder = MockAwareDocTestFinder() - optionflags = get_optionflags(self) + optionflags = get_optionflags(self.config) runner = _get_runner( verbose=False, optionflags=optionflags, @@ -591,25 +573,8 @@ class DoctestModule(Module): ) -def _setup_fixtures(doctest_item: DoctestItem) -> FixtureRequest: - """Used by DoctestTextfile and DoctestItem to setup fixture information.""" - - def func() -> None: - pass - - doctest_item.funcargs = {} # type: ignore[attr-defined] - fm = doctest_item.session._fixturemanager - doctest_item._fixtureinfo = fm.getfixtureinfo( # type: ignore[attr-defined] - node=doctest_item, func=func, cls=None, funcargs=False - ) - fixture_request = FixtureRequest(doctest_item, _ispytest=True) - fixture_request._fillfixtures() - return fixture_request - - -def _init_checker_class() -> Type["doctest.OutputChecker"]: +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 @@ -652,7 +617,7 @@ def _init_checker_class() -> Type["doctest.OutputChecker"]: if not allow_unicode and not allow_bytes and not allow_number: return False - def remove_prefixes(regex: Pattern[str], txt: str) -> str: + def remove_prefixes(regex: re.Pattern[str], txt: str) -> str: return re.sub(regex, r"\1\2", txt) if allow_unicode: @@ -674,9 +639,9 @@ def _init_checker_class() -> Type["doctest.OutputChecker"]: if len(wants) != len(gots): return got offset = 0 - for w, g in zip(wants, gots): - fraction: Optional[str] = w.group("fraction") - exponent: Optional[str] = w.group("exponent1") + for w, g in zip(wants, gots, strict=True): + 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) @@ -695,7 +660,7 @@ def _init_checker_class() -> Type["doctest.OutputChecker"]: return LiteralsOutputChecker -def _get_checker() -> "doctest.OutputChecker": +def _get_checker() -> doctest.OutputChecker: """Return a doctest.OutputChecker subclass that supports some additional options: @@ -754,7 +719,7 @@ def _get_report_choice(key: str) -> int: @fixture(scope="session") -def doctest_namespace() -> Dict[str, Any]: +def doctest_namespace() -> dict[str, Any]: """Fixture that returns a :py:class:`dict` that will be injected into the namespace of doctests. diff --git a/venv/lib/python3.10/site-packages/_pytest/faulthandler.py b/venv/lib/python3.10/site-packages/_pytest/faulthandler.py index 36040bf..080cf58 100644 --- a/venv/lib/python3.10/site-packages/_pytest/faulthandler.py +++ b/venv/lib/python3.10/site-packages/_pytest/faulthandler.py @@ -1,31 +1,47 @@ +from __future__ import annotations + +from collections.abc import Generator import os import sys -from typing import Generator -import pytest 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]() -fault_handler_originally_enabled_key = StashKey[bool]() def pytest_addoption(parser: Parser) -> None: - help = ( + help_timeout = ( "Dump the traceback of all threads if a test takes " "more than TIMEOUT seconds to finish" ) - parser.addini("faulthandler_timeout", help, default=0.0) + help_exit_on_timeout = ( + "Exit the test process if a test takes more than " + "faulthandler_timeout seconds to finish" + ) + parser.addini("faulthandler_timeout", help_timeout, default=0.0) + parser.addini( + "faulthandler_exit_on_timeout", help_exit_on_timeout, type="bool", default=False + ) def pytest_configure(config: Config) -> None: import faulthandler - config.stash[fault_handler_stderr_fd_key] = os.dup(get_stderr_fileno()) - config.stash[fault_handler_originally_enabled_key] = faulthandler.is_enabled() + # 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]) @@ -37,9 +53,10 @@ def pytest_unconfigure(config: Config) -> None: 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] - if config.stash.get(fault_handler_originally_enabled_key, False): - # Re-enable the faulthandler if it was originally enabled. - faulthandler.enable(file=get_stderr_fileno()) + # 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: @@ -54,6 +71,7 @@ def get_stderr_fileno() -> int: # 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() @@ -61,20 +79,27 @@ def get_timeout_config_value(config: Config) -> float: return float(config.getini("faulthandler_timeout") or 0.0) -@pytest.hookimpl(hookwrapper=True, trylast=True) -def pytest_runtest_protocol(item: Item) -> Generator[None, None, None]: +def get_exit_on_timeout_config_value(config: Config) -> bool: + exit_on_timeout = config.getini("faulthandler_exit_on_timeout") + assert isinstance(exit_on_timeout, bool) + return exit_on_timeout + + +@pytest.hookimpl(wrapper=True, trylast=True) +def pytest_runtest_protocol(item: Item) -> Generator[None, object, object]: timeout = get_timeout_config_value(item.config) + exit_on_timeout = get_exit_on_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) + faulthandler.dump_traceback_later(timeout, file=stderr, exit=exit_on_timeout) try: - yield + return (yield) finally: faulthandler.cancel_dump_traceback_later() else: - yield + return (yield) @pytest.hookimpl(tryfirst=True) diff --git a/venv/lib/python3.10/site-packages/_pytest/fixtures.py b/venv/lib/python3.10/site-packages/_pytest/fixtures.py index 0462504..27846db 100644 --- a/venv/lib/python3.10/site-packages/_pytest/fixtures.py +++ b/venv/lib/python3.10/site-packages/_pytest/fixtures.py @@ -1,59 +1,61 @@ +# mypy: allow-untyped-defs +from __future__ import annotations + +import abc +from collections import defaultdict +from collections import deque +from collections import OrderedDict +from collections.abc import Callable +from collections.abc import Generator +from collections.abc import Iterable +from collections.abc import Iterator +from collections.abc import Mapping +from collections.abc import MutableMapping +from collections.abc import Sequence +from collections.abc import Set as AbstractSet import dataclasses import functools import inspect import os -import sys -import warnings -from collections import defaultdict -from collections import deque -from contextlib import suppress from pathlib import Path -from types import TracebackType +import sys +import types from typing import Any -from typing import Callable from typing import cast -from typing import Dict -from typing import Generator +from typing import Final +from typing import final from typing import Generic -from typing import Iterable -from typing import Iterator -from typing import List -from typing import MutableMapping from typing import NoReturn -from typing import Optional -from typing import Sequence -from typing import Set -from typing import Tuple -from typing import Type +from typing import overload 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 _format_args -from _pytest.compat import _PytestWrapper from _pytest.compat import assert_never -from _pytest.compat import final 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 overload from _pytest.compat import safe_getattr +from _pytest.compat import safe_isclass +from _pytest.compat import signature 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 @@ -62,81 +64,77 @@ 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 -from _pytest.stash import StashKey +from _pytest.warning_types import PytestRemovedIn9Warning +from _pytest.warning_types import PytestWarning + + +if sys.version_info < (3, 11): + from exceptiongroup import BaseExceptionGroup if TYPE_CHECKING: - from typing import Deque - - from _pytest.scope import _ScopeName - from _pytest.main import Session 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") +FixtureValue = TypeVar("FixtureValue", covariant=True) # 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]] -] +_FixtureFunc = Callable[..., FixtureValue] | Callable[..., Generator[FixtureValue]] # The type of FixtureDef.cached_result (type alias generic in fixture value). -_FixtureCachedResult = Union[ - Tuple[ +_FixtureCachedResult = ( + tuple[ # The result. FixtureValue, # Cache key. object, None, - ], - Tuple[ + ] + | tuple[ None, # Cache key. object, - # Exc info if raised. - Tuple[Type[BaseException], BaseException, TracebackType], - ], -] + # The exception and the original traceback. + tuple[BaseException, types.TracebackType | None], + ] +) -@dataclasses.dataclass(frozen=True) -class PseudoFixtureDef(Generic[FixtureValue]): - cached_result: "_FixtureCachedResult[FixtureValue]" - _scope: Scope - - -def pytest_sessionstart(session: "Session") -> None: +def pytest_sessionstart(session: Session) -> None: session._fixturemanager = FixtureManager(session) def get_scope_package( node: nodes.Item, - fixturedef: "FixtureDef[object]", -) -> Optional[Union[nodes.Item, nodes.Collector]]: + fixturedef: FixtureDef[object], +) -> nodes.Node | None: from _pytest.python import Package - current: Optional[Union[nodes.Item, nodes.Collector]] = node - fixture_package_name = "{}/{}".format(fixturedef.baseid, "__init__.py") - while current and ( - not isinstance(current, Package) or fixture_package_name != current.nodeid - ): - current = current.parent # type: ignore[assignment] - if current is None: - return node.session - return current + 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 -) -> Optional[Union[nodes.Item, nodes.Collector]]: +def get_scope_node(node: nodes.Node, scope: Scope) -> nodes.Node | None: + """Get the closest parent node (including self) which matches the given + scope. + + If there is no parent node for the scope (e.g. asking for class scope on a + Module, or on a Function when not defined in a class), returns None. + """ import _pytest.python if scope is Scope.Function: - return node.getparent(nodes.Item) + # 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: @@ -149,121 +147,12 @@ def get_scope_node( assert_never(scope) -# Used for storing artificial fixturedefs for direct parametrization. -name2pseudofixturedef_key = StashKey[Dict[str, "FixtureDef[Any]"]]() - - -def add_funcarg_pseudo_fixture_def( - collector: nodes.Collector, metafunc: "Metafunc", fixturemanager: "FixtureManager" -) -> None: - # This function will transform all collected calls to functions - # if they use direct funcargs (i.e. direct parametrization) - # because we want later test execution to be able to rely on - # an existing FixtureDef structure for all arguments. - # XXX we can probably avoid this algorithm if we modify CallSpec2 - # to directly care for creating the fixturedefs within its methods. - if not metafunc._calls[0].funcargs: - # This function call does not have direct parametrization. - return - # Collect funcargs of all callspecs into a list of values. - arg2params: Dict[str, List[object]] = {} - arg2scope: Dict[str, Scope] = {} - for callspec in metafunc._calls: - for argname, argvalue in callspec.funcargs.items(): - assert argname not in callspec.params - callspec.params[argname] = argvalue - arg2params_list = arg2params.setdefault(argname, []) - callspec.indices[argname] = len(arg2params_list) - arg2params_list.append(argvalue) - if argname not in arg2scope: - scope = callspec._arg2scope.get(argname, Scope.Function) - arg2scope[argname] = scope - callspec.funcargs.clear() - - # Register artificial FixtureDef's so that later at test execution - # time we can rely on a proper FixtureDef to exist for fixture setup. - arg2fixturedefs = metafunc._arg2fixturedefs - for argname, valuelist in arg2params.items(): - # 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. - scope = arg2scope[argname] - node = None - if scope is not Scope.Function: - node = get_scope_node(collector, scope) - if node is None: - assert scope is Scope.Class and isinstance( - collector, _pytest.python.Module - ) - # Use module-level collector for class-scope (for now). - node = collector - if node is None: - name2pseudofixturedef = None - else: - default: Dict[str, FixtureDef[Any]] = {} - name2pseudofixturedef = node.stash.setdefault( - name2pseudofixturedef_key, default - ) - if name2pseudofixturedef is not None and argname in name2pseudofixturedef: - arg2fixturedefs[argname] = [name2pseudofixturedef[argname]] - else: - fixturedef = FixtureDef( - fixturemanager=fixturemanager, - baseid="", - argname=argname, - func=get_direct_param_fixture_func, - scope=arg2scope[argname], - params=valuelist, - unittest=False, - ids=None, - ) - arg2fixturedefs[argname] = [fixturedef] - if name2pseudofixturedef is not None: - name2pseudofixturedef[argname] = fixturedef - - -def getfixturemarker(obj: object) -> Optional["FixtureFunctionMarker"]: - """Return fixturemarker or None if it doesn't exist or raised - exceptions.""" - return cast( - Optional[FixtureFunctionMarker], - safe_getattr(obj, "_pytestfixturefunction", None), - ) - - -# Parametrized fixture key, helper alias for code below. -_Key = Tuple[object, ...] - - -def get_parametrized_fixture_keys(item: nodes.Item, scope: Scope) -> Iterator[_Key]: - """Return list of keys for all parametrized arguments which match - the specified scope.""" - assert scope is not Scope.Function - try: - callspec = item.callspec # type: ignore[attr-defined] - except AttributeError: - pass - else: - cs: CallSpec2 = callspec - # cs.indices.items() is random order of argnames. Need to - # sort this so that different calls to - # get_parametrized_fixture_keys will be deterministic. - for argname, param_index in sorted(cs.indices.items()): - if cs._arg2scope[argname] != scope: - continue - if scope is Scope.Session: - key: _Key = (argname, param_index) - elif scope is Scope.Package: - key = (argname, param_index, item.path.parent) - elif scope is Scope.Module: - key = (argname, param_index, item.path) - elif scope is Scope.Class: - item_cls = item.cls # type: ignore[attr-defined] - key = (argname, param_index, item.path, item_cls) - else: - assert_never(scope) - yield key +# TODO: Try to use FixtureFunctionDefinition instead of the marker +def getfixturemarker(obj: object) -> FixtureFunctionMarker | None: + """Return fixturemarker or None if it doesn't exist""" + if isinstance(obj, FixtureFunctionDefinition): + return obj._fixture_function_marker + return None # Algorithm for sorting on a per-parametrized resource setup basis. @@ -272,61 +161,109 @@ def get_parametrized_fixture_keys(item: nodes.Item, scope: Scope) -> Iterator[_K # setups and teardowns. -def reorder_items(items: Sequence[nodes.Item]) -> List[nodes.Item]: - argkeys_cache: Dict[Scope, Dict[nodes.Item, Dict[_Key, None]]] = {} - items_by_argkey: Dict[Scope, Dict[_Key, Deque[nodes.Item]]] = {} +@dataclasses.dataclass(frozen=True) +class ParamArgKey: + """A key for a high-scoped parameter used by an item. + + For use as a hashable key in `reorder_items`. The combination of fields + is meant to uniquely identify a particular "instance" of a param, + potentially shared by multiple items in a scope. + """ + + #: The param name. + argname: str + param_index: int + #: For scopes Package, Module, Class, the path to the file (directory in + #: Package's case) of the package/module/class where the item is defined. + scoped_item_path: Path | None + #: For Class scope, the class where the item is defined. + item_cls: type | None + + +_V = TypeVar("_V") +OrderedSet = dict[_V, None] + + +def get_param_argkeys(item: nodes.Item, scope: Scope) -> Iterator[ParamArgKey]: + """Return all ParamArgKeys for item matching the specified high 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 ParamArgKey(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[ParamArgKey]]] = {} + items_by_argkey: dict[Scope, dict[ParamArgKey, OrderedDict[nodes.Item, None]]] = {} for scope in HIGH_SCOPES: - d: Dict[nodes.Item, Dict[_Key, None]] = {} - argkeys_cache[scope] = d - item_d: Dict[_Key, Deque[nodes.Item]] = defaultdict(deque) - items_by_argkey[scope] = item_d + scoped_argkeys_by_item = argkeys_by_item[scope] = {} + scoped_items_by_argkey = items_by_argkey[scope] = defaultdict(OrderedDict) for item in items: - keys = dict.fromkeys(get_parametrized_fixture_keys(item, scope), None) - if keys: - d[item] = keys - for key in keys: - item_d[key].append(item) - items_dict = dict.fromkeys(items, None) + argkeys = dict.fromkeys(get_param_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_dict, argkeys_cache, items_by_argkey, Scope.Session) + reorder_items_atscope( + items_set, argkeys_by_item, items_by_argkey, Scope.Session + ) ) -def fix_cache_order( - item: nodes.Item, - argkeys_cache: Dict[Scope, Dict[nodes.Item, Dict[_Key, None]]], - items_by_argkey: Dict[Scope, Dict[_Key, "Deque[nodes.Item]"]], -) -> None: - for scope in HIGH_SCOPES: - for key in argkeys_cache[scope].get(item, []): - items_by_argkey[scope][key].appendleft(item) - - def reorder_items_atscope( - items: Dict[nodes.Item, None], - argkeys_cache: Dict[Scope, Dict[nodes.Item, Dict[_Key, None]]], - items_by_argkey: Dict[Scope, Dict[_Key, "Deque[nodes.Item]"]], + items: OrderedSet[nodes.Item], + argkeys_by_item: Mapping[Scope, Mapping[nodes.Item, OrderedSet[ParamArgKey]]], + items_by_argkey: Mapping[ + Scope, Mapping[ParamArgKey, OrderedDict[nodes.Item, None]] + ], scope: Scope, -) -> Dict[nodes.Item, None]: +) -> OrderedSet[nodes.Item]: if scope is Scope.Function or len(items) < 3: return items - ignore: Set[Optional[_Key]] = set() - items_deque = deque(items) - items_done: Dict[nodes.Item, None] = {} + scoped_items_by_argkey = items_by_argkey[scope] - scoped_argkeys_cache = argkeys_cache[scope] + scoped_argkeys_by_item = argkeys_by_item[scope] + + ignore: set[ParamArgKey] = set() + items_deque = deque(items) + items_done: OrderedSet[nodes.Item] = {} while items_deque: - no_argkey_group: Dict[nodes.Item, None] = {} + 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_group: + if item in items_done or item in no_argkey_items: continue argkeys = dict.fromkeys( - (k for k in scoped_argkeys_cache.get(item, []) if k not in ignore), None + k for k in scoped_argkeys_by_item.get(item, ()) if k not in ignore ) if not argkeys: - no_argkey_group[item] = None + no_argkey_items[item] = None else: slicing_argkey, _ = argkeys.popitem() # We don't have to remove relevant items from later in the @@ -335,35 +272,64 @@ def reorder_items_atscope( i for i in scoped_items_by_argkey[slicing_argkey] if i in items ] for i in reversed(matching_items): - fix_cache_order(i, argkeys_cache, items_by_argkey) 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, ()): + argkey_dict = other_scoped_items_by_argkey[argkey] + if not hasattr(sys, "pypy_version_info"): + argkey_dict[i] = None + argkey_dict.move_to_end(i, last=False) + else: + # Work around a bug in PyPy: + # https://github.com/pypy/pypy/issues/5257 + # https://github.com/pytest-dev/pytest/issues/13312 + bkp = argkey_dict.copy() + argkey_dict.clear() + argkey_dict[i] = None + argkey_dict.update(bkp) break - if no_argkey_group: - no_argkey_group = reorder_items_atscope( - no_argkey_group, argkeys_cache, items_by_argkey, scope.next_lower() + if no_argkey_items: + reordered_no_argkey_items = reorder_items_atscope( + no_argkey_items, argkeys_by_item, items_by_argkey, scope.next_lower() ) - for item in no_argkey_group: - items_done[item] = None - ignore.add(slicing_argkey) + items_done.update(reordered_no_argkey_items) + if slicing_argkey is not None: + ignore.add(slicing_argkey) return items_done -def get_direct_param_fixture_func(request: "FixtureRequest") -> Any: - return request.param - - -@dataclasses.dataclass +@dataclasses.dataclass(frozen=True) class FuncFixtureInfo: - __slots__ = ("argnames", "initialnames", "names_closure", "name2fixturedefs") + """Fixture-related information for a fixture-requesting item (e.g. test + function). - # Original function argument names. - argnames: Tuple[str, ...] - # Argnames that function immediately requires. These include argnames + - # fixture names specified via usefixtures and via autouse=True in fixture - # definitions. - initialnames: Tuple[str, ...] - names_closure: List[str] - name2fixturedefs: Dict[str, Sequence["FixtureDef[Any]"]] + 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", "name2fixturedefs", "names_closure") + + # 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. @@ -376,11 +342,11 @@ class FuncFixtureInfo: tree. In this way the dependency tree can get pruned, and the closure of argnames may get reduced. """ - closure: Set[str] = set() + closure: set[str] = set() working_set = set(self.initialnames) while working_set: argname = working_set.pop() - # Argname may be smth not included in the original names_closure, + # 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 @@ -393,25 +359,34 @@ class FuncFixtureInfo: self.names_closure[:] = sorted(closure, key=self.names_closure.index) -class FixtureRequest: - """A request for a fixture from a test or fixture function. +class FixtureRequest(abc.ABC): + """The type of the ``request`` fixture. - A request object gives access to the requesting test context and has - an optional ``param`` attribute in case the fixture is parametrized - indirectly. + 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, *, _ispytest: bool = False) -> None: + 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) - self._pyfuncitem = pyfuncitem #: Fixture for which this request is being performed. - self.fixturename: Optional[str] = None - self._scope = Scope.Function - self._fixture_defs: Dict[str, FixtureDef[Any]] = {} - fixtureinfo: FuncFixtureInfo = pyfuncitem._fixtureinfo - self._arg2fixturedefs = fixtureinfo.name2fixturedefs.copy() - self._arg2index: Dict[str, int] = {} - self._fixturemanager: FixtureManager = pyfuncitem.session._fixturemanager + 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 @@ -423,61 +398,44 @@ class FixtureRequest: self.param: Any @property - def scope(self) -> "_ScopeName": + 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], + requested_scope: Scope, + ) -> None: + raise NotImplementedError() + @property - def fixturenames(self) -> List[str]: + def fixturenames(self) -> list[str]: """Names of all active fixtures in this request.""" - result = list(self._pyfuncitem._fixtureinfo.names_closure) + 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).""" - scope = self._scope - if scope is Scope.Function: - # This might also be a non-function Item despite its attribute name. - node: Optional[Union[nodes.Item, nodes.Collector]] = self._pyfuncitem - elif scope is Scope.Package: - # FIXME: _fixturedef is not defined on FixtureRequest (this class), - # but on FixtureRequest (a subclass). - node = get_scope_package(self._pyfuncitem, self._fixturedef) # type: ignore[attr-defined] - 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, 'Could not obtain a node for scope "{}" for function {!r}'.format( - scope, self._pyfuncitem - ) - return node - - def _getnextfixturedef(self, argname: str) -> "FixtureDef[Any]": - fixturedefs = self._arg2fixturedefs.get(argname, None) - if fixturedefs is None: - # We arrive here because of a dynamic call to - # getfixturevalue(argname) usage which was naturally - # not known at parsing/collection time. - assert self._pyfuncitem.parent is not None - parentid = self._pyfuncitem.parent.nodeid - fixturedefs = self._fixturemanager.getfixturedefs(argname, parentid) - # TODO: Fix this type ignore. Either add assert or adjust types. - # Can this be None here? - self._arg2fixturedefs[argname] = fixturedefs # type: ignore[assignment] - # fixturedefs list is immutable so we maintain a decreasing index. - index = self._arg2index.get(argname, 0) - 1 - if fixturedefs is None or (-index > len(fixturedefs)): - raise FixtureLookupError(argname, self) - self._arg2index[argname] = index - return fixturedefs[index] + raise NotImplementedError() @property def config(self) -> Config: """The pytest config object associated with this request.""" - return self._pyfuncitem.config # type: ignore[no-any-return] + return self._pyfuncitem.config @property def function(self): @@ -500,27 +458,25 @@ class FixtureRequest: @property def instance(self): """Instance (can be None) on which test function was collected.""" - # unittest support hack, see _pytest.unittest.TestCaseFunction. - try: - return self._pyfuncitem._testcase - except AttributeError: - function = getattr(self, "function", None) - return getattr(function, "__self__", None) + 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") - return self._pyfuncitem.getparent(_pytest.python.Module).obj + 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") - # TODO: Remove ignore once _pyfuncitem is properly typed. - return self._pyfuncitem.path # type: ignore + return self._pyfuncitem.path @property def keywords(self) -> MutableMapping[str, Any]: @@ -529,17 +485,17 @@ class FixtureRequest: return node.keywords @property - def session(self) -> "Session": + def session(self) -> Session: """Pytest session object.""" - return self._pyfuncitem.session # type: ignore[no-any-return] + 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.""" - # XXX usually this method is shadowed by fixturedef specific ones. - self.node.addfinalizer(finalizer) + raise NotImplementedError() - def applymarker(self, marker: Union[str, MarkDecorator]) -> None: + 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 @@ -550,20 +506,13 @@ class FixtureRequest: """ self.node.add_marker(marker) - def raiseerror(self, msg: Optional[str]) -> NoReturn: + def raiseerror(self, msg: str | None) -> NoReturn: """Raise a FixtureLookupError exception. :param msg: An optional custom error message. """ - raise self._fixturemanager.FixtureLookupError(None, self, msg) - - def _fillfixtures(self) -> None: - item = self._pyfuncitem - fixturenames = getattr(item, "fixturenames", self.fixturenames) - for argname in fixturenames: - if argname not in item.funcargs: - item.funcargs[argname] = self.getfixturevalue(argname) + raise FixtureLookupError(None, self, msg) def getfixturevalue(self, argname: str) -> Any: """Dynamically run a named fixture function. @@ -582,6 +531,11 @@ class FixtureRequest: :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. ' @@ -589,200 +543,258 @@ class FixtureRequest: ) return fixturedef.cached_result[0] - def _get_active_fixturedef( - self, argname: str - ) -> Union["FixtureDef[object]", PseudoFixtureDef[object]]: - try: - return self._fixture_defs[argname] - except KeyError: - try: - fixturedef = self._getnextfixturedef(argname) - except FixtureLookupError: - if argname == "request": - cached_result = (self, [0], None) - return PseudoFixtureDef(cached_result, Scope.Function) - raise - # Remove indent to prevent the python3 exception - # from leaking into the call. - self._compute_fixture_value(fixturedef) - self._fixture_defs[argname] = fixturedef - return fixturedef + def _iter_chain(self) -> Iterator[SubRequest]: + """Yield all SubRequests in the chain, from self up. - def _get_fixturestack(self) -> List["FixtureDef[Any]"]: - current = self - values: List[FixtureDef[Any]] = [] - while isinstance(current, SubRequest): - values.append(current._fixturedef) # type: ignore[has-type] - current = current._parent_request - values.reverse() - return values - - def _compute_fixture_value(self, fixturedef: "FixtureDef[object]") -> None: - """Create a SubRequest based on "self" and call the execute method - of the given FixtureDef object. - - This will force the FixtureDef object to throw away any previous - results and compute a new fixture value, which will be stored into - the FixtureDef object itself. + Note: does *not* yield the TopRequest. """ - # prepare a subrequest object before calling fixture function - # (latter managed by fixturedef) - argname = fixturedef.argname - funcitem = self._pyfuncitem - scope = fixturedef._scope + current = self + while isinstance(current, SubRequest): + yield current + current = current._parent_request + + def _get_active_fixturedef(self, argname: str) -> FixtureDef[object]: + if argname == "request": + return RequestFixtureDef(self) + + # 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 = funcitem.callspec + 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] - # If a parametrize invocation set a scope it will override - # the static scope defined with the fixture function. - with suppress(KeyError): - scope = callspec._arg2scope[argname] + # The parametrize invocation scope overrides the fixture's scope. + scope = callspec._arg2scope[argname] else: param = NOTSET param_index = 0 - has_params = fixturedef.params is not None - fixtures_not_supported = getattr(funcitem, "nofuncargs", False) - if has_params and fixtures_not_supported: - msg = ( - "{name} does not support fixtures, maybe unittest.TestCase subclass?\n" - "Node id: {nodeid}\n" - "Function type: {typename}" - ).format( - name=funcitem.name, - nodeid=funcitem.nodeid, - typename=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) - msg = ( - "The requested fixture has no parameter defined for test:\n" - " {}\n\n" - "Requested fixture '{}' defined in:\n{}" - "\n\nRequested here:\n{}:{}".format( - funcitem.nodeid, - fixturedef.argname, - getlocation(fixturedef.func, funcitem.config.rootpath), - source_path_str, - source_lineno, - ) - ) - fail(msg, pytrace=False) - + scope = fixturedef._scope + self._check_fixturedef_without_param(fixturedef) + # The parametrize invocation scope only controls caching behavior while + # allowing wider-scoped fixtures to keep depending on the parametrized + # fixture. Scope control is enforced for parametrized fixtures + # by recreating the whole fixture tree on parameter change. + # Hence `fixturedef._scope`, not `scope`. + self._check_scope(fixturedef, fixturedef._scope) subrequest = SubRequest( self, scope, param, param_index, fixturedef, _ispytest=True ) - # Check if a higher-level scoped fixture accesses a lower level one. - subrequest._check_scope(argname, self._scope, scope) - try: - # Call the fixture function. - fixturedef.execute(request=subrequest) - finally: - self._schedule_finalizers(fixturedef, subrequest) + # Make sure the fixture value is cached, running it if it isn't + fixturedef.execute(request=subrequest) - def _schedule_finalizers( - self, fixturedef: "FixtureDef[object]", subrequest: "SubRequest" - ) -> None: - # If fixture function failed it might have registered finalizers. - subrequest.node.addfinalizer(lambda: fixturedef.finish(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, - argname: str, - invoking_scope: Scope, + requested_fixturedef: FixtureDef[object], requested_scope: Scope, ) -> None: - if argname == "request": - return - if invoking_scope > requested_scope: - # Try to report something helpful. - text = "\n".join(self._factorytraceback()) - fail( - f"ScopeMismatch: You tried to access the {requested_scope.value} scoped " - f"fixture {argname} with a {invoking_scope.value} scoped request object, " - f"involved factories:\n{text}", - pytrace=False, - ) + # TopRequest always has function scope so always valid. + pass - def _factorytraceback(self) -> List[str]: - lines = [] - for fixturedef in self._get_fixturestack(): - factory = fixturedef.func - fs, lineno = getfslineno(factory) - if isinstance(fs, Path): - session: Session = self._pyfuncitem.session - p = bestrelpath(session.path, fs) - else: - p = fs - args = _format_args(factory) - lines.append("%s:%d: def %s%s" % (p, lineno + 1, factory.__name__, args)) - return lines + @property + def node(self): + return self._pyfuncitem def __repr__(self) -> str: - return "" % (self.node) + 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): - """A sub request for handling getting a fixture from a test function/fixture.""" + """The type of the ``request`` fixture in a fixture function requested + (transitively) by a test function.""" def __init__( self, - request: "FixtureRequest", + request: FixtureRequest, scope: Scope, param: Any, param_index: int, - fixturedef: "FixtureDef[object]", + fixturedef: FixtureDef[object], *, _ispytest: bool = False, ) -> None: - check_ispytest(_ispytest) - self._parent_request = request - self.fixturename = fixturedef.argname + 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 = param_index - self._scope = scope - self._fixturedef = fixturedef - self._pyfuncitem = request._pyfuncitem - self._fixture_defs = request._fixture_defs - self._arg2fixturedefs = request._arg2fixturedefs - self._arg2index = request._arg2index - self._fixturemanager = request._fixturemanager + self.param_index: Final = param_index def __repr__(self) -> str: return f"" - 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.""" - self._fixturedef.addfinalizer(finalizer) + @property + def _scope(self) -> Scope: + return self._scope_field - def _schedule_finalizers( - self, fixturedef: "FixtureDef[object]", subrequest: "SubRequest" + @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], + requested_scope: Scope, ) -> None: - # If the executing fixturedef was not explicitly requested in the argument list (via - # getfixturevalue inside the fixture call) then ensure this fixture def will be finished - # first. - if fixturedef.argname not in self.fixturenames: - fixturedef.addfinalizer( - functools.partial(self._fixturedef.finish, request=self) + 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() ) - super()._schedule_finalizers(fixturedef, subrequest) + 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) + sig = signature(factory) + return f"{path}:{lineno + 1}: def {factory.__name__}{sig}" + + def addfinalizer(self, finalizer: Callable[[], object]) -> None: + self._fixturedef.addfinalizer(finalizer) @final @@ -790,19 +802,28 @@ class FixtureLookupError(LookupError): """Could not return a requested fixture (missing or invalid).""" def __init__( - self, argname: Optional[str], request: FixtureRequest, msg: Optional[str] = None + 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] = [] + 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 + # This function currently makes an assumption that a non-None msg means we + # have a non-empty `self.fixturestack`. This is currently true, but if + # somebody at some point want to extend the use of FixtureLookupError to + # new cases it might break. + # Add the assert to make it clearer to developer that this will fail, otherwise + # it crashes because `fspath` does not get set due to `stack` being empty. + assert self.msg is None or self.fixturestack, ( + "formatrepr assumptions broken, rewrite it to handle it" + ) if msg is not None: # The last fixture raise an error, let's present # it at the requesting side. @@ -825,14 +846,15 @@ class FixtureLookupError(LookupError): if msg is None: fm = self.request._fixturemanager available = set() - parentid = self.request._pyfuncitem.parent.nodeid + parent = self.request._pyfuncitem.parent + assert parent is not None for name, fixturedefs in fm._arg2fixturedefs.items(): - faclist = list(fm._matchfactories(fixturedefs, parentid)) + faclist = list(fm._matchfactories(fixturedefs, parent)) if faclist: available.add(name) if self.argname in available: - msg = " recursive dependency involving fixture '{}' detected".format( - self.argname + msg = ( + f" recursive dependency involving fixture '{self.argname}' detected" ) else: msg = f"fixture '{self.argname}' not found" @@ -845,11 +867,11 @@ class FixtureLookupError(LookupError): class FixtureLookupErrorRepr(TerminalRepr): def __init__( self, - filename: Union[str, "os.PathLike[str]"], + filename: str | os.PathLike[str], firstlineno: int, tblines: Sequence[str], errorstring: str, - argname: Optional[str], + argname: str | None, ) -> None: self.tblines = tblines self.errorstring = errorstring @@ -873,23 +895,14 @@ class FixtureLookupErrorRepr(TerminalRepr): red=True, ) tw.line() - tw.line("%s:%d" % (os.fspath(self.filename), self.firstlineno + 1)) - - -def fail_fixturefunc(fixturefunc, msg: str) -> NoReturn: - fs, lineno = getfslineno(fixturefunc) - location = f"{fs}:{lineno + 1}" - source = _pytest._code.Source(fixturefunc) - fail(msg + ":\n\n" + str(source.indent()) + "\n" + location, pytrace=False) + tw.line(f"{os.fspath(self.filename)}:{self.firstlineno + 1}") def call_fixture_func( - fixturefunc: "_FixtureFunc[FixtureValue]", request: FixtureRequest, kwargs + fixturefunc: _FixtureFunc[FixtureValue], request: FixtureRequest, kwargs ) -> FixtureValue: - if is_generator(fixturefunc): - fixturefunc = cast( - Callable[..., Generator[FixtureValue, None, None]], fixturefunc - ) + if inspect.isgeneratorfunction(fixturefunc): + fixturefunc = cast(Callable[..., Generator[FixtureValue]], fixturefunc) generator = fixturefunc(**kwargs) try: fixture_result = next(generator) @@ -912,58 +925,65 @@ def _teardown_yield_fixture(fixturefunc, it) -> None: except StopIteration: pass else: - fail_fixturefunc(fixturefunc, "fixture function has more than one 'yield'") + 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]", + scope_callable: Callable[[str, Config], _ScopeName], fixture_name: str, config: Config, -) -> "_ScopeName": +) -> _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( - "Error evaluating {} while defining fixture '{}'.\n" - "Expected a function with the signature (*, fixture_name, config)".format( - scope_callable, fixture_name - ) + 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( - "Expected {} to return a 'str' while defining fixture '{}', but it returned:\n" - "{!r}".format(scope_callable, fixture_name, result), + 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.""" + """A container for a fixture definition. + + Note: At this time, only explicitly documented fields and methods are + considered public stable API. + """ def __init__( self, - fixturemanager: "FixtureManager", - baseid: Optional[str], + config: Config, + baseid: str | None, argname: str, - func: "_FixtureFunc[FixtureValue]", - scope: Union[Scope, "_ScopeName", Callable[[str, Config], "_ScopeName"], None], - params: Optional[Sequence[object]], - unittest: bool = False, - ids: Optional[ - Union[Tuple[Optional[object], ...], Callable[[Any], Optional[object]]] - ] = None, + 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, + # only used in a deprecationwarning msg, can be removed in pytest9 + _autouse: bool = False, ) -> None: - self._fixturemanager = fixturemanager + 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 parent of the node's - # nodeid (see the `iterparentnodeids` function for what constitutes a - # "parent" and a "prefix" in this context). + # 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. @@ -972,43 +992,42 @@ class FixtureDef(Generic[FixtureValue]): # directory path relative to the rootdir. # # For other plugins, the baseid is the empty string (always matches). - self.baseid = baseid or "" + 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 = baseid is not None + self.has_location: Final = baseid is not None # The fixture factory function. - self.func = func + self.func: Final = func # The name by which the fixture may be requested. - self.argname = argname + self.argname: Final = argname if scope is None: scope = Scope.Function elif callable(scope): - scope = _eval_scope_callable(scope, argname, fixturemanager.config) + 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 = scope + self._scope: Final = scope # If the fixture is directly parametrized, the parameter values. - self.params: Optional[Sequence[object]] = params + 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 = ids + self.ids: Final = ids # The names requested by the fixtures. - self.argnames = getfuncargnames(func, name=argname, is_method=unittest) - # Whether the fixture was collected from a unittest TestCase class. - # Note that it really only makes sense to define autouse fixtures in - # unittest TestCases. - self.unittest = unittest + 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: Optional[_FixtureCachedResult[FixtureValue]] = None - self._finalizers: List[Callable[[], object]] = [] + self.cached_result: _FixtureCachedResult[FixtureValue] | None = None + self._finalizers: Final[list[Callable[[], object]]] = [] + + # only used to emit a deprecationwarning, can be removed in pytest9 + self._autouse = _autouse @property - def scope(self) -> "_ScopeName": + def scope(self) -> _ScopeName: """Scope string, one of "function", "class", "module", "package", "session".""" return self._scope.value @@ -1016,92 +1035,137 @@ class FixtureDef(Generic[FixtureValue]): self._finalizers.append(finalizer) def finish(self, request: SubRequest) -> None: - exc = None - try: - while self._finalizers: - try: - func = self._finalizers.pop() - func() - except BaseException as e: - # XXX Only first exception will be seen by user, - # ideally all should be reported. - if exc is None: - exc = e - if exc: - raise exc - finally: - ihook = request.node.ihook - 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 = [] + 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: - # Get required arguments and register our own finish() - # with their finalization. + """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) - if argname != "request": - # PseudoFixtureDef is only for "request". - assert isinstance(fixturedef, FixtureDef) - fixturedef.addfinalizer(functools.partial(self.finish, request=request)) + # 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. + requested_fixtures_that_should_finalize_us.append(fixturedef) - my_cache_key = self.cache_key(request) + # Check for (and return) cached value/exception. if self.cached_result is not None: - # note: comparison with `==` can fail (or be expensive) for e.g. - # numpy arrays (#6497). + request_cache_key = self.cache_key(request) cache_key = self.cached_result[1] - if my_cache_key is cache_key: + 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: - _, val, tb = self.cached_result[2] - raise val.with_traceback(tb) + exc, exc_tb = self.cached_result[2] + raise exc.with_traceback(exc_tb) else: - result = self.cached_result[0] - return result + return self.cached_result[0] # 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 - result = ihook.pytest_fixture_setup(fixturedef=self, request=request) + try: + # Setup the fixture, run the code in it, and cache the value + # in self.cached_result. + result: FixtureValue = 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 request.param_index if not hasattr(request, "param") else request.param + return getattr(request, "param", None) def __repr__(self) -> str: - return "".format( - self.argname, self.scope, self.baseid + return f"" + + +class RequestFixtureDef(FixtureDef[FixtureRequest]): + """A custom FixtureDef for the special "request" fixture. + + A new one is generated on-demand whenever "request" is requested. + """ + + def __init__(self, request: FixtureRequest) -> None: + super().__init__( + config=request.config, + baseid=None, + argname="request", + func=lambda: request, + scope=Scope.Function, + params=None, + _ispytest=True, ) + self.cached_result = (request, [0], None) + + def addfinalizer(self, finalizer: Callable[[], object]) -> None: + pass def resolve_fixture_function( fixturedef: FixtureDef[FixtureValue], request: FixtureRequest -) -> "_FixtureFunc[FixtureValue]": +) -> _FixtureFunc[FixtureValue]: """Get the actual callable that can be called to obtain the fixture - value, dealing with unittest-specific instances and bound methods.""" + value.""" fixturefunc = fixturedef.func - if fixturedef.unittest: - if request.instance is not None: - # Bind the unbound method to the TestCase instance. - fixturefunc = fixturedef.func.__get__(request.instance) # type: ignore[union-attr] - else: - # The fixture function needs to be bound to the actual - # request.instance so that code working with "fixturedef" behaves - # as expected. - if request.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( - request.instance, fixturefunc.__self__.__class__ # type: ignore[union-attr] - ): - return fixturefunc - fixturefunc = getimfunc(fixturedef.func) - if fixturefunc != fixturedef.func: - fixturefunc = fixturefunc.__get__(request.instance) # type: ignore[union-attr] + # 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 @@ -1111,152 +1175,166 @@ def pytest_fixture_setup( """Execution of fixture setup.""" kwargs = {} for argname in fixturedef.argnames: - fixdef = request._get_active_fixturedef(argname) - assert fixdef.cached_result is not None - result, arg_cache_key, exc = fixdef.cached_result - request._check_scope(argname, request._scope, fixdef._scope) - kwargs[argname] = result + kwargs[argname] = request.getfixturevalue(argname) fixturefunc = resolve_fixture_function(fixturedef, request) my_cache_key = fixturedef.cache_key(request) + + if inspect.isasyncgenfunction(fixturefunc) or inspect.iscoroutinefunction( + fixturefunc + ): + auto_str = " with autouse=True" if fixturedef._autouse else "" + + warnings.warn( + PytestRemovedIn9Warning( + f"{request.node.name!r} requested an async fixture " + f"{request.fixturename!r}{auto_str}, with no plugin or hook that " + "handled it. This is usually an error, as pytest does not natively " + "support it. " + "This will turn into an error in pytest 9.\n" + "See: https://docs.pytest.org/en/stable/deprecations.html#sync-test-depending-on-async-fixture" + ), + # no stacklevel will point at users code, so we just point here + stacklevel=1, + ) + try: result = call_fixture_func(fixturefunc, request, kwargs) - except TEST_OUTCOME: - exc_info = sys.exc_info() - assert exc_info[0] is not None - if isinstance( - exc_info[1], skip.Exception - ) and not fixturefunc.__name__.startswith("xunit_setup"): - exc_info[1]._use_item_location = True # type: ignore[attr-defined] - fixturedef.cached_result = (None, my_cache_key, exc_info) + 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 _ensure_immutable_ids( - ids: Optional[Union[Sequence[Optional[object]], Callable[[Any], Optional[object]]]] -) -> Optional[Union[Tuple[Optional[object], ...], Callable[[Any], Optional[object]]]]: - if ids is None: - return None - if callable(ids): - return ids - return tuple(ids) - - -def _params_converter( - params: Optional[Iterable[object]], -) -> Optional[Tuple[object, ...]]: - return tuple(params) if params is not None else None - - -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.""" - message = ( - '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." - ).format(name=fixture_marker.name or function.__name__) - - @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: "Union[_ScopeName, Callable[[str, Config], _ScopeName]]" - params: Optional[Tuple[object, ...]] + scope: _ScopeName | Callable[[str, Config], _ScopeName] + params: tuple[object, ...] | None autouse: bool = False - ids: Optional[ - Union[Tuple[Optional[object], ...], Callable[[Any], Optional[object]]] - ] = None - name: Optional[str] = None + 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: + def __call__(self, function: FixtureFunction) -> FixtureFunctionDefinition: if inspect.isclass(function): raise ValueError("class fixtures not supported (maybe in the future)") - if getattr(function, "_pytestfixturefunction", False): + if isinstance(function, FixtureFunctionDefinition): raise ValueError( - "fixture is being applied more than once to the same function" + f"@pytest.fixture is being applied more than once to the same function {function.__name__!r}" ) - function = wrap_function_to_error_out_if_called_directly(function, self) + if hasattr(function, "pytestmark"): + warnings.warn(MARKED_FIXTURE, stacklevel=2) + + fixture_definition = FixtureFunctionDefinition( + function=function, fixture_function_marker=self, _ispytest=True + ) name = self.name or function.__name__ if name == "request": location = getlocation(function) fail( - "'request' is a reserved word for fixtures, use another name:\n {}".format( - location - ), + 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 + return fixture_definition + + +# TODO: paramspec/return type annotation tracking and storing +class FixtureFunctionDefinition: + def __init__( + self, + *, + function: Callable[..., Any], + fixture_function_marker: FixtureFunctionMarker, + instance: object | None = None, + _ispytest: bool = False, + ) -> None: + check_ispytest(_ispytest) + self.name = fixture_function_marker.name or function.__name__ + # In order to show the function that this fixture contains in messages. + # Set the __name__ to be same as the function __name__ or the given fixture name. + self.__name__ = self.name + self._fixture_function_marker = fixture_function_marker + if instance is not None: + self._fixture_function = cast( + Callable[..., Any], function.__get__(instance) + ) + else: + self._fixture_function = function + functools.update_wrapper(self, function) + + def __repr__(self) -> str: + return f"" + + def __get__(self, instance, owner=None): + """Behave like a method if the function it was applied to was a method.""" + return FixtureFunctionDefinition( + function=self._fixture_function, + fixture_function_marker=self._fixture_function_marker, + instance=instance, + _ispytest=True, + ) + + def __call__(self, *args: Any, **kwds: Any) -> Any: + message = ( + f'Fixture "{self.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" + ) + fail(message, pytrace=False) + + def _get_wrapped_function(self) -> Callable[..., Any]: + return self._fixture_function @overload def fixture( - fixture_function: FixtureFunction, + fixture_function: Callable[..., object], *, - scope: "Union[_ScopeName, Callable[[str, Config], _ScopeName]]" = ..., - params: Optional[Iterable[object]] = ..., + scope: _ScopeName | Callable[[str, Config], _ScopeName] = ..., + params: Iterable[object] | None = ..., autouse: bool = ..., - ids: Optional[ - Union[Sequence[Optional[object]], Callable[[Any], Optional[object]]] - ] = ..., - name: Optional[str] = ..., -) -> FixtureFunction: - ... + ids: Sequence[object | None] | Callable[[Any], object | None] | None = ..., + name: str | None = ..., +) -> FixtureFunctionDefinition: ... @overload -def fixture( # noqa: F811 +def fixture( fixture_function: None = ..., *, - scope: "Union[_ScopeName, Callable[[str, Config], _ScopeName]]" = ..., - params: Optional[Iterable[object]] = ..., + scope: _ScopeName | Callable[[str, Config], _ScopeName] = ..., + params: Iterable[object] | None = ..., autouse: bool = ..., - ids: Optional[ - Union[Sequence[Optional[object]], Callable[[Any], Optional[object]]] - ] = ..., - name: Optional[str] = None, -) -> FixtureFunctionMarker: - ... + ids: Sequence[object | None] | Callable[[Any], object | None] | None = ..., + name: str | None = None, +) -> FixtureFunctionMarker: ... -def fixture( # noqa: F811 - fixture_function: Optional[FixtureFunction] = None, +def fixture( + fixture_function: FixtureFunction | None = None, *, - scope: "Union[_ScopeName, Callable[[str, Config], _ScopeName]]" = "function", - params: Optional[Iterable[object]] = None, + scope: _ScopeName | Callable[[str, Config], _ScopeName] = "function", + params: Iterable[object] | None = None, autouse: bool = False, - ids: Optional[ - Union[Sequence[Optional[object]], Callable[[Any], Optional[object]]] - ] = None, - name: Optional[str] = None, -) -> Union[FixtureFunctionMarker, FixtureFunction]: + ids: Sequence[object | None] | Callable[[Any], object | None] | None = None, + name: str | None = None, +) -> FixtureFunctionMarker | FixtureFunctionDefinition: """Decorator to mark a fixture factory function. This decorator can be used, with or without parameters, to define a @@ -1357,7 +1435,7 @@ def pytestconfig(request: FixtureRequest) -> Config: Example:: def test_foo(pytestconfig): - if pytestconfig.getoption("verbose") > 0: + if pytestconfig.get_verbosity() > 0: ... """ @@ -1371,6 +1449,58 @@ def pytest_addoption(parser: Parser) -> None: 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: @@ -1390,7 +1520,7 @@ class FixtureManager: relevant for a particular function. An initial list of fixtures is assembled like this: - - ini-defined usefixtures + - config-defined usefixtures - autouse-marked fixtures along the collection chain up from the function - usefixtures markers at module/class/function level - test function funcargs @@ -1404,92 +1534,105 @@ class FixtureManager: by a lookup of their FuncFixtureInfo. """ - FixtureLookupError = FixtureLookupError - FixtureLookupErrorRepr = FixtureLookupErrorRepr - - def __init__(self, session: "Session") -> None: + def __init__(self, session: Session) -> None: self.session = session self.config: Config = session.config - self._arg2fixturedefs: Dict[str, List[FixtureDef[Any]]] = {} - self._holderobjseen: Set[object] = set() + # 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: Dict[str, List[str]] = { + self._nodeid_autousenames: Final[dict[str, list[str]]] = { "": self.config.getini("usefixtures"), } session.config.pluginmanager.register(self, "funcmanage") - def _get_direct_parametrize_args(self, node: nodes.Node) -> List[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: List[str] = [] - 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.extend(p_argnames) - - return parametrize_argnames - def getfixtureinfo( - self, node: nodes.Node, func, cls, funcargs: bool = True + self, + node: nodes.Item, + func: Callable[..., object] | None, + cls: type | None, ) -> FuncFixtureInfo: - if funcargs and not getattr(node, "nofuncargs", False): + """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) - usefixtures = tuple( - arg for mark in node.iter_markers(name="usefixtures") for arg in mark.args - ) - initialnames = usefixtures + argnames - fm = node.session._fixturemanager - initialnames, names_closure, arg2fixturedefs = fm.getfixtureclosure( - initialnames, node, ignore_args=self._get_direct_parametrize_args(node) + 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) -> None: - nodeid = None - try: - p = absolutepath(plugin.__file__) # type: ignore[attr-defined] - except AttributeError: - pass + 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: - # Construct the base nodeid which is later used to check - # what fixtures are visible for particular tests (as denoted - # by their test id). - if p.name.startswith("conftest.py"): - try: - nodeid = str(p.parent.relative_to(self.config.rootpath)) - except ValueError: - nodeid = "" - if nodeid == ".": - nodeid = "" - if os.sep != nodes.SEP: - nodeid = nodeid.replace(os.sep, nodes.SEP) + nodeid = None self.parsefactories(plugin, nodeid) - def _getautousenames(self, nodeid: str) -> Iterator[str]: - """Return the names of autouse fixtures applicable to nodeid.""" - for parentnodeid in nodes.iterparentnodeids(nodeid): - basenames = self._nodeid_autousenames.get(parentnodeid) + 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 marker_node, mark in node.iter_markers_with_node(name="usefixtures"): + if not mark.args: + marker_node.warn( + PytestWarning( + f"usefixtures() in {node.nodeid} without arguments has no effect" + ) + ) + yield from mark.args + def getfixtureclosure( self, - fixturenames: Tuple[str, ...], parentnode: nodes.Node, - ignore_args: Sequence[str] = (), - ) -> Tuple[Tuple[str, ...], List[str], Dict[str, Sequence[FixtureDef[Any]]]]: + 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 @@ -1497,34 +1640,47 @@ class FixtureManager: # to re-discover fixturedefs again for each fixturename # (discovering matching fixtures for a given name/node is expensive). - parentid = parentnode.nodeid - fixturenames_closure = list(self._getautousenames(parentid)) + fixturenames_closure = list(initialnames) - def merge(otherlist: Iterable[str]) -> None: - for arg in otherlist: - if arg not in fixturenames_closure: - fixturenames_closure.append(arg) + arg2fixturedefs: dict[str, Sequence[FixtureDef[Any]]] = {} - merge(fixturenames) + # Track the index for each fixture name in the simulated stack. + # Needed for handling override chains correctly, similar to _get_active_fixturedef. + # Using negative indices: -1 is the most specific (last), -2 is second to last, etc. + current_indices: dict[str, int] = {} - # At this point, fixturenames_closure contains what we call "initialnames", - # which is a set of fixturenames the function immediately requests. We - # need to return it as well, so save this. - initialnames = tuple(fixturenames_closure) + def process_argname(argname: str) -> None: + # Optimization: already processed this argname. + if current_indices.get(argname) == -1: + return - 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, parentid) - if fixturedefs: - arg2fixturedefs[argname] = fixturedefs - merge(fixturedefs[-1].argnames) + if argname not in fixturenames_closure: + fixturenames_closure.append(argname) + + if argname in ignore_args: + return + + fixturedefs = arg2fixturedefs.get(argname) + if not fixturedefs: + fixturedefs = self.getfixturedefs(argname, parentnode) + if not fixturedefs: + # Fixture not defined or not visible (will error during runtest). + return + arg2fixturedefs[argname] = fixturedefs + + index = current_indices.get(argname, -1) + if -index > len(fixturedefs): + # Exhausted the override chain (will error during runtest). + return + fixturedef = fixturedefs[index] + + current_indices[argname] = index - 1 + for dep in fixturedef.argnames: + process_argname(dep) + current_indices[argname] = index + + for name in initialnames: + process_argname(name) def sort_by_scope(arg_name: str) -> Scope: try: @@ -1535,9 +1691,9 @@ class FixtureManager: return fixturedefs[-1]._scope fixturenames_closure.sort(key=sort_by_scope, reverse=True) - return initialnames, fixturenames_closure, arg2fixturedefs + return fixturenames_closure, arg2fixturedefs - def pytest_generate_tests(self, metafunc: "Metafunc") -> None: + 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]: @@ -1582,35 +1738,85 @@ class FixtureManager: # Try next super fixture, if any. - def pytest_collection_modifyitems(self, items: List[nodes.Item]) -> None: + 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, + _autouse=autouse, + ) + + 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, - *, - unittest: bool = ..., ) -> None: raise NotImplementedError() @overload - def parsefactories( # noqa: F811 + def parsefactories( self, node_or_obj: object, - nodeid: Optional[str], - *, - unittest: bool = ..., + nodeid: str | None, ) -> None: raise NotImplementedError() - def parsefactories( # noqa: F811 + def parsefactories( self, - node_or_obj: Union[nodes.Node, object], - nodeid: Union[str, NotSetType, None] = NOTSET, - *, - unittest: bool = False, + node_or_obj: nodes.Node | object, + nodeid: str | NotSetType | None = NOTSET, ) -> None: """Collect fixtures from a collection node or object. @@ -1618,7 +1824,7 @@ class FixtureManager: 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' visibilty. `nodeid` must not be specified in + 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 @@ -1636,78 +1842,206 @@ class FixtureManager: 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) - autousenames = [] for name in dir(holderobj): - # ugly workaround for one of the fspath deprecated property of node - # todo: safely generalize - if isinstance(holderobj, nodes.Node) and name == "fspath": - continue - # The attribute can be an arbitrary descriptor, so the attribute - # access below can raise. safe_getatt() ignores such exceptions. - obj = safe_getattr(holderobj, name, None) - marker = getfixturemarker(obj) - if not isinstance(marker, FixtureFunctionMarker): - # Magic globals with __getattr__ might have got us a wrong - # fixture attribute. - continue + # access below can raise. safe_getattr() ignores such exceptions. + obj_ub = safe_getattr(holderobj_tp, name, None) + if type(obj_ub) is FixtureFunctionDefinition: + marker = obj_ub._fixture_function_marker + if marker.name: + fixture_name = marker.name + else: + fixture_name = name - if marker.name: - name = marker.name + # OK we know it is a fixture -- now safe to look up on the _instance_. + try: + obj = getattr(holderobj, name) + # if the fixture is named in the decorator we cannot find it in the module + except AttributeError: + obj = obj_ub - # 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. - obj = get_real_method(obj, holderobj) + func = obj._get_wrapped_function() - fixture_def = FixtureDef( - fixturemanager=self, - baseid=nodeid, - argname=name, - func=obj, - scope=marker.scope, - params=marker.params, - unittest=unittest, - ids=marker.ids, - ) - - 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 marker.autouse: - autousenames.append(name) - - if autousenames: - self._nodeid_autousenames.setdefault(nodeid or "", []).extend(autousenames) + self._register_fixture( + name=fixture_name, + nodeid=nodeid, + func=func, + scope=marker.scope, + params=marker.params, + ids=marker.ids, + autouse=marker.autouse, + ) def getfixturedefs( - self, argname: str, nodeid: str - ) -> Optional[Sequence[FixtureDef[Any]]]: - """Get a list of fixtures which are applicable to the given node id. + self, argname: str, node: nodes.Node + ) -> Sequence[FixtureDef[Any]] | None: + """Get FixtureDefs for a fixture name which are applicable + to a given node. - :param str argname: Name of the fixture to search for. - :param str nodeid: Full node id of the requesting test. - :rtype: Sequence[FixtureDef] + 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, nodeid)) + return tuple(self._matchfactories(fixturedefs, node)) def _matchfactories( - self, fixturedefs: Iterable[FixtureDef[Any]], nodeid: str + self, fixturedefs: Iterable[FixtureDef[Any]], node: nodes.Node ) -> Iterator[FixtureDef[Any]]: - parentnodeids = set(nodes.iterparentnodeids(nodeid)) + 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/python3.10/site-packages/_pytest/freeze_support.py b/venv/lib/python3.10/site-packages/_pytest/freeze_support.py index 9f8ea23..959ff07 100644 --- a/venv/lib/python3.10/site-packages/_pytest/freeze_support.py +++ b/venv/lib/python3.10/site-packages/_pytest/freeze_support.py @@ -1,12 +1,13 @@ """Provides a function to report all internal modules for using freezing tools.""" + +from __future__ import annotations + +from collections.abc import Iterator import types -from typing import Iterator -from typing import List -from typing import Union -def freeze_includes() -> List[str]: +def freeze_includes() -> list[str]: """Return a list of module names used by pytest that should be included by cx_freeze.""" import _pytest @@ -16,7 +17,7 @@ def freeze_includes() -> List[str]: def _iter_all_modules( - package: Union[str, types.ModuleType], + package: str | types.ModuleType, prefix: str = "", ) -> Iterator[str]: """Iterate over the names of all modules that can be found in the given @@ -34,7 +35,7 @@ def _iter_all_modules( else: # Type ignored because typeshed doesn't define ModuleType.__path__ # (only defined on packages). - package_path = package.__path__ # type: ignore[attr-defined] + package_path = package.__path__ path, prefix = package_path[0], package.__name__ + "." for _, name, is_package in pkgutil.iter_modules([path]): if is_package: diff --git a/venv/lib/python3.10/site-packages/_pytest/helpconfig.py b/venv/lib/python3.10/site-packages/_pytest/helpconfig.py index ea16c43..6a22c9f 100644 --- a/venv/lib/python3.10/site-packages/_pytest/helpconfig.py +++ b/venv/lib/python3.10/site-packages/_pytest/helpconfig.py @@ -1,44 +1,58 @@ +# mypy: allow-untyped-defs """Version info, help messages, tracing configuration.""" + +from __future__ import annotations + +import argparse +from collections.abc import Generator +from collections.abc import Sequence import os import sys -from argparse import Action -from typing import List -from typing import Optional -from typing import Union +from typing import Any -import pytest 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. +class HelpAction(argparse.Action): + """An argparse Action that will raise a PrintHelp 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. + This prevents argparse from raising UsageError when `--help` is used along + with 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. + + To opt in to this behavior, the parse caller must set + `namespace._raise_print_help = True`. Otherwise it just sets the option. """ - def __init__(self, option_strings, dest=None, default=False, help=None): + def __init__( + self, option_strings: Sequence[str], dest: str, *, help: str | None = None + ) -> None: super().__init__( option_strings=option_strings, dest=dest, - const=True, - default=default, nargs=0, + const=True, + default=False, help=help, ) - def __call__(self, parser, namespace, values, option_string=None): + def __call__( + self, + parser: argparse.ArgumentParser, + namespace: argparse.Namespace, + values: str | Sequence[Any] | None, + option_string: str | None = None, + ) -> 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): + if getattr(namespace, "_raise_print_help", False): raise PrintHelp @@ -53,14 +67,14 @@ def pytest_addoption(parser: Parser) -> None: help="Display pytest version and information about plugins. " "When given twice, also display information about plugins.", ) - group._addoption( + group._addoption( # private to use reserved lower-case short option "-h", "--help", action=HelpAction, dest="help", help="Show help message and configuration info", ) - group._addoption( + group._addoption( # private to use reserved lower-case short option "-p", action="append", dest="plugins", @@ -68,7 +82,14 @@ def pytest_addoption(parser: Parser) -> None: 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`.", + "`no:doctest`. See also --disable-plugin-autoload.", + ) + group.addoption( + "--disable-plugin-autoload", + action="store_true", + default=False, + help="Disable plugin auto-loading through entry point packaging metadata. " + "Only plugins explicitly specified in -p or env var PYTEST_PLUGINS will be loaded.", ) group.addoption( "--traceconfig", @@ -88,79 +109,78 @@ def pytest_addoption(parser: Parser) -> None: "This file is opened with 'w' and truncated as a result, care advised. " "Default: pytestdebug.log.", ) - group._addoption( + group._addoption( # private to use reserved lower-case short option "-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`.", + help='Override configuration option with "option=value" style, ' + "e.g. `-o strict_xfail=True -o cache_dir=cache`.", ) -@pytest.hookimpl(hookwrapper=True) -def pytest_cmdline_parse(): - outcome = yield - config: Config = outcome.get_result() +@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-%s, " - "python-%s\ncwd=%s\nargs=%s\n\n" - % ( + "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("writing pytest debug information to %s\n" % path) + sys.stderr.write(f"writing pytest debug information to {path}\n") def unset_tracing() -> None: debugfile.close() - sys.stderr.write("wrote pytest debug information to %s\n" % debugfile.name) + 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: + +def show_version_verbose(config: Config) -> None: + """Show verbose pytest version installation, including plugins.""" + 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") + + +def pytest_cmdline_main(config: Config) -> int | ExitCode | None: + # Note: a single `--version` argument is handled directly by `Config.main()` to avoid starting up the entire + # pytest infrastructure just to display the version (#13574). if config.option.version > 1: - sys.stdout.write( - "This is pytest version {}, imported from {}\n".format( - pytest.__version__, pytest.__file__ - ) - ) - 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) -> Optional[Union[int, ExitCode]]: - if config.option.version > 0: - showversion(config) - return 0 + show_version_verbose(config) + return ExitCode.OK elif config.option.help: config._do_configure() showhelp(config) config._ensure_unconfigure() - return 0 + return ExitCode.OK return None def showhelp(config: Config) -> None: import textwrap - reporter: Optional[TerminalReporter] = config.pluginmanager.get_plugin( + reporter: TerminalReporter | None = config.pluginmanager.get_plugin( "terminalreporter" ) assert reporter is not None @@ -168,22 +188,20 @@ def showhelp(config: Config) -> None: 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:" + "[pytest] configuration options in the first " + "pytest.toml|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" + for name in config._parser._inidict: + help, type, _default = config._parser._inidict[name] if help is None: raise TypeError(f"help argument cannot be None for {name}") spec = f"{name} ({type}):" - tw.write(" %s" % spec) + tw.write(f" {spec}") spec_len = len(spec) if spec_len > (indent_len - 3): # Display help starting at a new line. @@ -211,10 +229,19 @@ def showhelp(config: Config) -> None: tw.line() tw.line("Environment variables:") vars = [ + ( + "CI", + "When set to a non-empty 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"), + ("PYTEST_DEBUG_TEMPROOT", "Override the system temporary directory"), + ("PYTEST_THEME", "The Pygments style to use for code output"), + ("PYTEST_THEME_MODE", "Set the PYTEST_THEME to be either 'dark' or 'light'"), ] for name, help in vars: tw.line(f" {name:<24} {help}") @@ -231,17 +258,13 @@ def showhelp(config: Config) -> None: for warningreport in reporter.stats.get("warnings", []): tw.line("warning : " + warningreport.message, red=True) - return -conftest_options = [("pytest_plugins", "list of plugin names to load")] - - -def getpluginversioninfo(config: Config) -> List[str]: +def getpluginversioninfo(config: Config) -> list[str]: lines = [] plugininfo = config.pluginmanager.list_plugin_distinfo() if plugininfo: - lines.append("setuptools registered plugins:") + 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}" @@ -249,7 +272,7 @@ def getpluginversioninfo(config: Config) -> List[str]: return lines -def pytest_report_header(config: Config) -> List[str]: +def pytest_report_header(config: Config) -> list[str]: lines = [] if config.option.debug or config.option.traceconfig: lines.append(f"using: pytest-{pytest.__version__}") diff --git a/venv/lib/python3.10/site-packages/_pytest/hookspec.py b/venv/lib/python3.10/site-packages/_pytest/hookspec.py index 1f7c368..c5bcc36 100644 --- a/venv/lib/python3.10/site-packages/_pytest/hookspec.py +++ b/venv/lib/python3.10/site-packages/_pytest/hookspec.py @@ -1,31 +1,33 @@ +# 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 collections.abc import Mapping +from collections.abc import Sequence from pathlib import Path from typing import Any -from typing import Dict -from typing import List -from typing import Mapping -from typing import Optional -from typing import Sequence -from typing import Tuple from typing import TYPE_CHECKING -from typing import Union from pluggy import HookspecMarker -from _pytest.deprecated import WARNING_CMDLINE_PREPARSE_HOOK +from .deprecated import HOOK_LEGACY_PATH_ARG + if TYPE_CHECKING: import pdb + from typing import Literal import warnings - from typing_extensions import Literal - from _pytest._code.code import ExceptionRepr 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 import _PluggyPlugin from _pytest.config.argparsing import Parser from _pytest.fixtures import FixtureDef from _pytest.fixtures import SubRequest @@ -42,7 +44,6 @@ if TYPE_CHECKING: from _pytest.runner import CallInfo from _pytest.terminal import TerminalReporter from _pytest.terminal import TestShortLogReport - from _pytest.compat import LEGACY_PATH hookspec = HookspecMarker("pytest") @@ -53,51 +54,62 @@ hookspec = HookspecMarker("pytest") @hookspec(historic=True) -def pytest_addhooks(pluginmanager: "PytestPluginManager") -> None: +def pytest_addhooks(pluginmanager: PytestPluginManager) -> None: """Called at plugin registration time to allow adding new hooks via a call to - ``pluginmanager.add_hookspecs(module_or_class, prefix)``. + :func:`pluginmanager.add_hookspecs(module_or_class, prefix) `. - :param pytest.PytestPluginManager pluginmanager: The pytest plugin manager. + :param pluginmanager: The pytest plugin manager. .. note:: - This hook is incompatible with ``hookwrapper=True``. + 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", manager: "PytestPluginManager" + plugin: _PluggyPlugin, + plugin_name: str, + manager: PytestPluginManager, ) -> None: """A new pytest plugin got registered. :param plugin: The plugin module or instance. - :param pytest.PytestPluginManager manager: pytest plugin manager. + :param plugin_name: The name by which the plugin is registered. + :param manager: The pytest plugin manager. .. note:: - This hook is incompatible with ``hookwrapper=True``. + 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, +def pytest_addoption(parser: Parser, pluginmanager: PytestPluginManager) -> None: + """Register argparse-style options and config-style config values, called once at the beginning of a test run. - .. note:: - - This function should be implemented only in plugins or ``conftest.py`` - files situated at the tests root directory due to how pytest - :ref:`discovers plugins during startup `. - - :param pytest.Parser parser: + :param parser: To add command line options, call :py:func:`parser.addoption(...) `. - To add ini-file values call :py:func:`parser.addini(...) + To add config-file values call :py:func:`parser.addini(...) `. - :param pytest.PytestPluginManager pluginmanager: - The pytest plugin manager, which can be used to install :py:func:`hookspec`'s - or :py:func:`hookimpl`'s and allow one plugin to call another plugin's hooks + :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 @@ -107,30 +119,39 @@ def pytest_addoption(parser: "Parser", pluginmanager: "PytestPluginManager") -> retrieve the value of a command line option. - :py:func:`config.getini(name) ` to retrieve - a value read from an ini-style file. + a value read from a configuration 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 ``hookwrapper=True``. + 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: +def pytest_configure(config: Config) -> None: """Allow plugins and conftest files to perform initial configuration. - This hook is called for every plugin and initial conftest file - after command line options have been parsed. - - After that, the hook is called for other conftest files as they are - imported. - .. note:: - This hook is incompatible with ``hookwrapper=True``. + This hook is incompatible with hook wrappers. - :param pytest.Config config: The pytest config object. + :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. """ @@ -142,62 +163,61 @@ def pytest_configure(config: "Config") -> None: @hookspec(firstresult=True) def pytest_cmdline_parse( - pluginmanager: "PytestPluginManager", args: List[str] -) -> Optional["Config"]: + 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 will only be called for plugin classes passed to the + 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. """ -@hookspec(warn_on_impl=WARNING_CMDLINE_PREPARSE_HOOK) -def pytest_cmdline_preparse(config: "Config", args: List[str]) -> None: - """(**Deprecated**) modify command line arguments before option parsing. +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. - This hook is considered deprecated and will be removed in a future pytest version. Consider - using :hook:`pytest_load_initial_conftests` instead. - - .. note:: - This hook will not be called for ``conftest.py`` files, only for setuptools plugins. - - :param config: The pytest config object. + :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") -> Optional[Union["ExitCode", int]]: - """Called for performing the main command line action. The default - implementation will invoke the configure hooks and runtest_mainloop. +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 + ======================= -def pytest_load_initial_conftests( - early_config: "Config", parser: "Parser", args: List[str] -) -> None: - """Called to implement the loading of initial conftest files ahead - of command line option parsing. - - .. note:: - This hook will not be called for ``conftest.py`` files, only for setuptools plugins. - - :param early_config: The pytest config object. - :param args: Arguments passed on the command line. - :param parser: To add command line options. + This hook is only called for :ref:`initial conftests `. """ @@ -207,7 +227,7 @@ def pytest_load_initial_conftests( @hookspec(firstresult=True) -def pytest_collection(session: "Session") -> Optional[object]: +def pytest_collection(session: Session) -> object | None: """Perform the collection phase for the given session. Stops at first non-None result, see :ref:`firstresult`. @@ -240,33 +260,65 @@ def pytest_collection(session: "Session") -> Optional[object]: 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"] + 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: +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) +@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" -) -> Optional[bool]: - """Return True to prevent considering this path for collection. + 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. @@ -274,6 +326,7 @@ def pytest_ignore_collect( 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. @@ -281,65 +334,151 @@ def pytest_ignore_collect( 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" -) -> "Optional[Collector]": + 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: +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: +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: +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: +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") -> "Optional[CollectReport]": +def pytest_make_collect_report(collector: Collector) -> CollectReport | None: """Perform :func:`collector.collect() ` and return a :class:`~pytest.CollectReport`. @@ -347,6 +486,13 @@ def pytest_make_collect_report(collector: "Collector") -> "Optional[CollectRepor :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. """ @@ -355,10 +501,17 @@ def pytest_make_collect_report(collector: "Collector") -> "Optional[CollectRepor # ------------------------------------------------------------------------- -@hookspec(firstresult=True) +@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 -) -> Optional["Module"]: + 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. @@ -368,6 +521,7 @@ def pytest_pycollect_makemodule( 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 @@ -375,13 +529,20 @@ def pytest_pycollect_makemodule( 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: Union["Module", "Class"], name: str, obj: object -) -> Union[None, "Item", "Collector", List[Union["Item", "Collector"]]]: + 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`. @@ -394,32 +555,51 @@ def pytest_pycollect_makeitem( 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") -> Optional[object]: +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: +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 -) -> Optional[str]: +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``. @@ -430,7 +610,12 @@ def pytest_make_parametrize_id( :param config: The pytest config object. :param val: The parametrized value. - :param str argname: The automatic parameter name produced by pytest. + :param argname: The automatic parameter name produced by pytest. + + Use in conftest plugins + ======================= + + Any conftest file can implement this hook. """ @@ -440,7 +625,7 @@ def pytest_make_parametrize_id( @hookspec(firstresult=True) -def pytest_runtestloop(session: "Session") -> Optional[object]: +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 @@ -457,13 +642,16 @@ def pytest_runtestloop(session: "Session") -> Optional[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: "Optional[Item]" -) -> Optional[object]: +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): @@ -476,7 +664,7 @@ def pytest_runtest_protocol( - ``pytest_runtest_logreport(report)`` - ``pytest_exception_interact(call, report)`` if an interactive exception occurred - - Call phase, if the the setup passed and the ``setuponly`` pytest option is not set: + - 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)`` @@ -495,12 +683,15 @@ def pytest_runtest_protocol( 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, Optional[int], str] -) -> None: +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. @@ -509,11 +700,17 @@ def pytest_runtest_logstart( :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, Optional[int], str] + nodeid: str, location: tuple[str, int | None, str] ) -> None: """Called at the end of running the runtest protocol for a single item. @@ -523,10 +720,16 @@ def pytest_runtest_logfinish( :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: +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 @@ -536,20 +739,32 @@ def pytest_runtest_setup(item: "Item") -> None: :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: +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: Optional["Item"]) -> None: +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()`` @@ -564,13 +779,17 @@ def pytest_runtest_teardown(item: "Item", nextitem: Optional["Item"]) -> None: 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]" -) -> Optional["TestReport"]: +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. @@ -580,39 +799,63 @@ def pytest_runtest_makereport( :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: +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: Union["CollectReport", "TestReport"], -) -> Optional[Dict[str, Any]]: + 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], -) -> Optional[Union["CollectReport", "TestReport"]]: + 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. """ @@ -623,11 +866,11 @@ def pytest_report_from_serializable( @hookspec(firstresult=True) def pytest_fixture_setup( - fixturedef: "FixtureDef[Any]", request: "SubRequest" -) -> Optional[object]: + fixturedef: FixtureDef[Any], request: SubRequest +) -> object | None: """Perform fixture setup execution. - :param fixturdef: + :param fixturedef: The fixture definition object. :param request: The fixture request object. @@ -640,20 +883,34 @@ def pytest_fixture_setup( 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" + 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 fixturdef: + :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. """ @@ -662,29 +919,44 @@ def pytest_fixture_post_finalizer( # ------------------------------------------------------------------------- -def pytest_sessionstart(session: "Session") -> None: +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: Union[int, "ExitCode"], + 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: +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. """ @@ -694,8 +966,8 @@ def pytest_unconfigure(config: "Config") -> None: def pytest_assertrepr_compare( - config: "Config", op: str, left: object, right: object -) -> Optional[List[str]]: + 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 @@ -707,10 +979,16 @@ def pytest_assertrepr_compare( :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: +def pytest_assertion_pass(item: Item, lineno: int, orig: str, expl: str) -> None: """Called whenever an assertion passes. .. versionadded:: 5.0 @@ -720,13 +998,22 @@ def pytest_assertion_pass(item: "Item", lineno: int, orig: str, expl: str) -> No 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: + This hook must be explicitly enabled by the :confval:`enable_assertion_pass_hook` + configuration option: - .. code-block:: ini + .. tab:: toml - [pytest] - enable_assertion_pass_hook=true + .. code-block:: toml + + [pytest] + enable_assertion_pass_hook = true + + .. tab:: ini + + .. 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. @@ -735,6 +1022,12 @@ def pytest_assertion_pass(item: "Item", lineno: int, orig: str, expl: str) -> No :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. """ @@ -743,13 +1036,21 @@ def pytest_assertion_pass(item: "Item", lineno: int, orig: str, expl: str) -> No # ------------------------------------------------------------------------- +@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" -) -> Union[str, List[str]]: + 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:: @@ -759,25 +1060,31 @@ def pytest_report_header( # type:ignore[empty-body] If you want to have your line(s) displayed first, use :ref:`trylast=True `. - .. note:: - - This function should be implemented only in plugins or ``conftest.py`` - files situated at the tests root directory due to how pytest - :ref:`discovers plugins during startup `. - .. 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", + config: Config, start_path: Path, - startdir: "LEGACY_PATH", - items: Sequence["Item"], -) -> Union[str, List[str]]: + startdir: LEGACY_PATH, + items: Sequence[Item], +) -> str | list[str]: """Return a string or list of strings to be displayed after collection has finished successfully. @@ -787,6 +1094,7 @@ def pytest_report_collectionfinish( # type:ignore[empty-body] :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. @@ -801,13 +1109,18 @@ def pytest_report_collectionfinish( # type:ignore[empty-body] 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: Union["CollectReport", "TestReport"], config: "Config" -) -> "TestShortLogReport | Tuple[str, str, Union[str, Tuple[str, Mapping[str, bool]]]]": + 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. @@ -829,13 +1142,18 @@ def pytest_report_teststatus( # type:ignore[empty-body] :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", + terminalreporter: TerminalReporter, + exitstatus: ExitCode, + config: Config, ) -> None: """Add a section to terminal summary reporting. @@ -845,21 +1163,26 @@ def pytest_terminal_summary( .. 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']", + warning_message: warnings.WarningMessage, + when: Literal["config", "collect", "runtest"], nodeid: str, - location: Optional[Tuple[str, int, 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 :py:func:`warnings.catch_warnings`, and contains - the same attributes as the parameters of :py:func:`warnings.showwarning`. + 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: @@ -869,7 +1192,8 @@ def pytest_warning_recorded( * ``"runtest"``: during test execution. :param nodeid: - Full id of the item. + 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 @@ -877,6 +1201,13 @@ def pytest_warning_recorded( 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. """ @@ -886,8 +1217,8 @@ def pytest_warning_recorded( def pytest_markeval_namespace( # type:ignore[empty-body] - config: "Config", -) -> Dict[str, Any]: + config: Config, +) -> dict[str, Any]: """Called when constructing the globals dictionary used for evaluating string conditions in xfail/skipif markers. @@ -900,6 +1231,12 @@ def pytest_markeval_namespace( # type:ignore[empty-body] :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. """ @@ -909,9 +1246,9 @@ def pytest_markeval_namespace( # type:ignore[empty-body] def pytest_internalerror( - excrepr: "ExceptionRepr", - excinfo: "ExceptionInfo[BaseException]", -) -> Optional[bool]: + excrepr: ExceptionRepr, + excinfo: ExceptionInfo[BaseException], +) -> bool | None: """Called for internal errors. Return True to suppress the fallback handling of printing an @@ -919,31 +1256,41 @@ def pytest_internalerror( :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[Union[KeyboardInterrupt, Exit]]", + 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: Union["Item", "Collector"], - call: "CallInfo[Any]", - report: Union["CollectReport", "TestReport"], + 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:`CollectReport`. + 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:`TestReport`. + 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``. @@ -954,10 +1301,16 @@ def pytest_exception_interact( 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: +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 @@ -965,10 +1318,15 @@ def pytest_enter_pdb(config: "Config", pdb: "pdb.Pdb") -> None: :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: +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 @@ -976,4 +1334,9 @@ def pytest_leave_pdb(config: "Config", pdb: "pdb.Pdb") -> None: :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/python3.10/site-packages/_pytest/junitxml.py b/venv/lib/python3.10/site-packages/_pytest/junitxml.py index ed259e4..ae8d2b9 100644 --- a/venv/lib/python3.10/site-packages/_pytest/junitxml.py +++ b/venv/lib/python3.10/site-packages/_pytest/junitxml.py @@ -1,3 +1,4 @@ +# mypy: allow-untyped-defs """Report test results in JUnit-XML format, for use with Jenkins and build integration servers. @@ -6,21 +7,16 @@ 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 collections.abc import Callable import functools import os import platform import re import xml.etree.ElementTree as ET -from datetime import datetime -from typing import Callable -from typing import Dict -from typing import List -from typing import Match -from typing import Optional -from typing import Tuple -from typing import Union -import pytest from _pytest import nodes from _pytest import timing from _pytest._code.code import ExceptionRepr @@ -32,6 +28,7 @@ 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"]() @@ -48,18 +45,18 @@ def bin_xml_escape(arg: object) -> str: The idea is to escape visually for the user rather than for XML itself. """ - def repl(matchobj: Match[str]) -> str: + def repl(matchobj: re.Match[str]) -> str: i = ord(matchobj.group()) if i <= 0xFF: - return "#x%02X" % i + return f"#x{i:02X}" else: - return "#x%04X" % i + 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]" + "[^\u0009\u000a\u000d\u0020-\u007e\u0080-\ud7ff\ue000-\ufffd\u10000-\u10ffff]" ) return re.sub(illegal_xml_re, repl, str(arg)) @@ -74,10 +71,10 @@ def merge_family(left, right) -> None: left.update(result) -families = {} -families["_base"] = {"testcase": ["classname", "name"]} -families["_base_legacy"] = {"testcase": ["file", "line", "url"]} - +families = { # pylint: disable=dict-init-mutate + "_base": {"testcase": ["classname", "name"]}, + "_base_legacy": {"testcase": ["file", "line", "url"]}, +} # xUnit 1.x inherits legacy attributes. families["xunit1"] = families["_base"].copy() merge_family(families["xunit1"], families["_base_legacy"]) @@ -87,15 +84,15 @@ families["xunit2"] = families["_base"] class _NodeReporter: - def __init__(self, nodeid: Union[str, TestReport], xml: "LogXML") -> None: + 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] = {} + 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) @@ -107,7 +104,7 @@ class _NodeReporter: def add_attribute(self, name: str, value: object) -> None: self.attrs[str(name)] = bin_xml_escape(value) - def make_properties_node(self) -> Optional[ET.Element]: + def make_properties_node(self) -> ET.Element | None: """Return a Junit node containing custom properties, if any.""" if self.properties: properties = ET.Element("properties") @@ -122,7 +119,7 @@ class _NodeReporter: classnames = names[:-1] if self.xml.prefix: classnames.insert(0, self.xml.prefix) - attrs: Dict[str, str] = { + attrs: dict[str, str] = { "classname": ".".join(classnames), "name": bin_xml_escape(names[-1]), "file": testreport.location[0], @@ -141,20 +138,20 @@ class _NodeReporter: # 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.keys(): + 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="%.3f" % self.duration) + 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: Optional[str] = None) -> None: + 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) @@ -199,7 +196,7 @@ class _NodeReporter: self._add_simple("skipped", "xfail-marked test passes unexpectedly") else: assert report.longrepr is not None - reprcrash: Optional[ReprFileLocation] = getattr( + reprcrash: ReprFileLocation | None = getattr( report.longrepr, "reprcrash", None ) if reprcrash is not None: @@ -219,9 +216,7 @@ class _NodeReporter: def append_error(self, report: TestReport) -> None: assert report.longrepr is not None - reprcrash: Optional[ReprFileLocation] = getattr( - report.longrepr, "reprcrash", None - ) + reprcrash: ReprFileLocation | None = getattr(report.longrepr, "reprcrash", None) if reprcrash is not None: reason = reprcrash.message else: @@ -248,7 +243,9 @@ class _NodeReporter: skipreason = skipreason[9:] details = f"{filename}:{lineno}: {skipreason}" - skipped = ET.Element("skipped", type="pytest.skip", message=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) @@ -258,7 +255,7 @@ class _NodeReporter: 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[assignment] + self.to_xml = lambda: data # type: ignore[method-assign] def _warn_incompatibility_with_xunit2( @@ -271,9 +268,7 @@ def _warn_incompatibility_with_xunit2( if xml is not None and xml.family not in ("xunit1", "legacy"): request.node.warn( PytestWarning( - "{fixture_name} is incompatible with junit_family '{family}' (use 'legacy' or 'xunit1')".format( - fixture_name=fixture_name, family=xml.family - ) + f"{fixture_name} is incompatible with junit_family '{xml.family}' (use 'legacy' or 'xunit1')" ) ) @@ -365,17 +360,16 @@ def record_testsuite_property(request: FixtureRequest) -> Callable[[str, object] `pytest-xdist `__ plugin. See :issue:`7767` for details. """ - __tracebackhide__ = True def record_func(name: str, value: object) -> None: - """No-op function in case --junitxml was not passed in the command-line.""" + """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 # noqa + record_func = xml.add_global_property return record_func @@ -450,7 +444,7 @@ def pytest_unconfigure(config: Config) -> None: config.pluginmanager.unregister(xml) -def mangle_test_address(address: str) -> List[str]: +def mangle_test_address(address: str) -> list[str]: path, possible_open_bracket, params = address.partition("[") names = path.split("::") # Convert file path to dotted path. @@ -465,7 +459,7 @@ class LogXML: def __init__( self, logfile, - prefix: Optional[str], + prefix: str | None, suite_name: str = "pytest", logging: str = "no", report_duration: str = "total", @@ -480,17 +474,15 @@ class LogXML: self.log_passing_tests = log_passing_tests self.report_duration = report_duration self.family = family - self.stats: Dict[str, int] = dict.fromkeys( + self.stats: dict[str, int] = dict.fromkeys( ["error", "passed", "failure", "skipped"], 0 ) - self.node_reporters: Dict[ - Tuple[Union[str, TestReport], object], _NodeReporter - ] = {} - self.node_reporters_ordered: List[_NodeReporter] = [] - self.global_properties: List[Tuple[str, str]] = [] + 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.open_reports: list[TestReport] = [] self.cnt_double_fail_tests = 0 # Replaces convenience family with real family. @@ -509,8 +501,8 @@ class LogXML: if reporter is not None: reporter.finalize() - def node_reporter(self, report: Union[TestReport, str]) -> _NodeReporter: - nodeid: Union[str, TestReport] = getattr(report, "nodeid", report) + 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) @@ -624,7 +616,7 @@ class LogXML: 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 == "total" or report.when == self.report_duration: + if self.report_duration in {"total", report.when}: reporter = self.node_reporter(report) reporter.duration += getattr(report, "duration", 0.0) @@ -642,7 +634,7 @@ class LogXML: reporter._add_simple("error", "internal error", str(excrepr)) def pytest_sessionstart(self) -> None: - self.suite_start_time = timing.time() + self.suite_start = timing.Instant() def pytest_sessionfinish(self) -> None: dirname = os.path.dirname(os.path.abspath(self.logfile)) @@ -650,8 +642,7 @@ class LogXML: 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 + duration = self.suite_start.elapsed() numtests = ( self.stats["passed"] @@ -669,8 +660,8 @@ class LogXML: failures=str(self.stats["failure"]), skipped=str(self.stats["skipped"]), tests=str(numtests), - time="%.3f" % suite_time_delta, - timestamp=datetime.fromtimestamp(self.suite_start_time).isoformat(), + time=f"{duration.seconds:.3f}", + timestamp=self.suite_start.as_utc().astimezone().isoformat(), hostname=platform.node(), ) global_properties = self._get_global_properties_node() @@ -679,18 +670,22 @@ class LogXML: for node_reporter in self.node_reporters_ordered: suite_node.append(node_reporter.to_xml()) testsuites = ET.Element("testsuites") + testsuites.set("name", "pytest tests") 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 pytest_terminal_summary( + self, terminalreporter: TerminalReporter, config: pytest.Config + ) -> None: + if config.get_verbosity() >= 0: + 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) -> Optional[ET.Element]: + 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") diff --git a/venv/lib/python3.10/site-packages/_pytest/legacypath.py b/venv/lib/python3.10/site-packages/_pytest/legacypath.py index af1d0c0..59e8ef6 100644 --- a/venv/lib/python3.10/site-packages/_pytest/legacypath.py +++ b/venv/lib/python3.10/site-packages/_pytest/legacypath.py @@ -1,17 +1,19 @@ +# 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 pathlib import Path -from typing import List -from typing import Optional +from typing import Final +from typing import final from typing import TYPE_CHECKING -from typing import Union from iniconfig import SectionWrapper from _pytest.cacheprovider import Cache -from _pytest.compat import final from _pytest.compat import LEGACY_PATH from _pytest.compat import legacy_path from _pytest.config import Config @@ -31,9 +33,8 @@ from _pytest.pytester import RunResult from _pytest.terminal import TerminalReporter from _pytest.tmpdir import TempPathFactory -if TYPE_CHECKING: - from typing_extensions import Final +if TYPE_CHECKING: import pexpect @@ -48,8 +49,8 @@ class Testdir: __test__ = False - CLOSE_STDIN: "Final" = Pytester.CLOSE_STDIN - TimeoutExpired: "Final" = Pytester.TimeoutExpired + CLOSE_STDIN: Final = Pytester.CLOSE_STDIN + TimeoutExpired: Final = Pytester.TimeoutExpired def __init__(self, pytester: Pytester, *, _ispytest: bool = False) -> None: check_ispytest(_ispytest) @@ -89,7 +90,6 @@ class Testdir: return self._pytester.chdir() def finalize(self) -> None: - """See :meth:`Pytester._finalize`.""" return self._pytester._finalize() def makefile(self, ext, *args, **kwargs) -> LEGACY_PATH: @@ -144,7 +144,7 @@ class Testdir: """See :meth:`Pytester.copy_example`.""" return legacy_path(self._pytester.copy_example(name)) - def getnode(self, config: Config, arg) -> Optional[Union[Item, Collector]]: + def getnode(self, config: Config, arg) -> Item | Collector | None: """See :meth:`Pytester.getnode`.""" return self._pytester.getnode(config, arg) @@ -152,7 +152,7 @@ class Testdir: """See :meth:`Pytester.getpathnode`.""" return self._pytester.getpathnode(path) - def genitems(self, colitems: List[Union[Item, Collector]]) -> List[Item]: + def genitems(self, colitems: list[Item | Collector]) -> list[Item]: """See :meth:`Pytester.genitems`.""" return self._pytester.genitems(colitems) @@ -204,9 +204,7 @@ class Testdir: source, configargs=configargs, withinit=withinit ) - def collect_by_name( - self, modcol: Collector, name: str - ) -> Optional[Union[Item, Collector]]: + 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) @@ -237,13 +235,11 @@ class Testdir: """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": + 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": + 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) @@ -270,7 +266,7 @@ class LegacyTestdirPlugin: @final @dataclasses.dataclass class TempdirFactory: - """Backward compatibility wrapper that implements :class:`py.path.local` + """Backward compatibility wrapper that implements ``py.path.local`` for :class:`TempPathFactory`. .. note:: @@ -289,11 +285,11 @@ class TempdirFactory: self._tmppath_factory = tmppath_factory def mktemp(self, basename: str, numbered: bool = True) -> LEGACY_PATH: - """Same as :meth:`TempPathFactory.mktemp`, but returns a :class:`py.path.local` object.""" + """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 :class:`py.path.local` object.""" + """Same as :meth:`TempPathFactory.getbasetemp`, but returns a ``py.path.local`` object.""" return legacy_path(self._tmppath_factory.getbasetemp().resolve()) @@ -308,16 +304,11 @@ class LegacyTmpdirPlugin: @staticmethod @fixture def tmpdir(tmp_path: Path) -> LEGACY_PATH: - """Return a temporary directory path object which is unique to each test - function invocation, created as a sub directory of the base temporary - directory. - - By default, a new base temporary directory is created each test session, - and old bases are removed after 3 sessions, to aid in debugging. If - ``--basetemp`` is used then it is cleared each session. See :ref:`base - temporary directory`. - - The returned object is a `legacy_path`_ object. + """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``. @@ -373,7 +364,7 @@ def Config_rootdir(self: Config) -> LEGACY_PATH: return legacy_path(str(self.rootpath)) -def Config_inifile(self: Config) -> Optional[LEGACY_PATH]: +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`. @@ -383,7 +374,7 @@ def Config_inifile(self: Config) -> Optional[LEGACY_PATH]: return legacy_path(str(self.inipath)) if self.inipath else None -def Session_stardir(self: Session) -> LEGACY_PATH: +def Session_startdir(self: Session) -> LEGACY_PATH: """The path from which pytest was invoked. Prefer to use ``startpath`` which is a :class:`pathlib.Path`. @@ -393,9 +384,7 @@ def Session_stardir(self: Session) -> LEGACY_PATH: return legacy_path(self.startpath) -def Config__getini_unknown_type( - self, name: str, type: str, value: Union[str, List[str]] -): +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 @@ -438,7 +427,7 @@ def pytest_load_initial_conftests(early_config: Config) -> None: mp.setattr(Config, "inifile", property(Config_inifile), raising=False) # Add Session.startdir property. - mp.setattr(Session, "startdir", property(Session_stardir), raising=False) + mp.setattr(Session, "startdir", property(Session_startdir), raising=False) # Add pathlist configuration type. mp.setattr(Config, "_getini_unknown_type", Config__getini_unknown_type) diff --git a/venv/lib/python3.10/site-packages/_pytest/logging.py b/venv/lib/python3.10/site-packages/_pytest/logging.py index 9f2f1c7..e4fed57 100644 --- a/venv/lib/python3.10/site-packages/_pytest/logging.py +++ b/venv/lib/python3.10/site-packages/_pytest/logging.py @@ -1,31 +1,33 @@ +# mypy: allow-untyped-defs """Access and control log capturing.""" -import io -import logging -import os -import re + +from __future__ import annotations + +from collections.abc import Generator +from collections.abc import Mapping +from collections.abc import Set as AbstractSet 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 -from typing import AbstractSet -from typing import Dict -from typing import Generator -from typing import List -from typing import Mapping -from typing import Optional -from typing import Tuple +import re +from types import TracebackType +from typing import final +from typing import Generic +from typing import Literal from typing import TYPE_CHECKING from typing import TypeVar -from typing import Union from _pytest import nodes from _pytest._io import TerminalWriter from _pytest.capture import CaptureManager -from _pytest.compat import final from _pytest.config import _strtobool from _pytest.config import Config from _pytest.config import create_terminal_writer @@ -39,10 +41,9 @@ from _pytest.main import Session from _pytest.stash import StashKey from _pytest.terminal import TerminalReporter + if TYPE_CHECKING: logging_StreamHandler = logging.StreamHandler[StringIO] - - from typing_extensions import Literal else: logging_StreamHandler = logging.StreamHandler @@ -50,7 +51,7 @@ DEFAULT_LOG_FORMAT = "%(levelname)-8s %(name)s:%(filename)s:%(lineno)d %(message 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]]]() +caplog_records_key = StashKey[dict[str, list[logging.LogRecord]]]() def _remove_ansi_escape_sequences(text: str) -> str: @@ -63,13 +64,14 @@ class DatetimeFormatter(logging.Formatter): :func:`time.strftime` in case of microseconds in format string. """ - def formatTime(self, record: LogRecord, datefmt=None) -> str: + 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` - dt = datetime(*ct[0:6], microsecond=round(record.msecs * 1000), tzinfo=tz) + # 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) @@ -94,7 +96,7 @@ class ColoredLevelFormatter(DatetimeFormatter): super().__init__(*args, **kwargs) self._terminalwriter = terminalwriter self._original_fmt = self._style._fmt - self._level_to_fmt_mapping: Dict[int, str] = {} + self._level_to_fmt_mapping: dict[int, str] = {} for level, color_opts in self.LOGLEVEL_COLOROPTS.items(): self.add_color_level(level, *color_opts) @@ -112,7 +114,6 @@ class ColoredLevelFormatter(DatetimeFormatter): .. 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: @@ -143,12 +144,12 @@ class PercentStyleMultiline(logging.PercentStyle): formats the message as if each line were logged separately. """ - def __init__(self, fmt: str, auto_indent: Union[int, str, bool, None]) -> None: + 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: Union[int, str, bool, None]) -> int: + 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 @@ -179,7 +180,6 @@ class PercentStyleMultiline(logging.PercentStyle): 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): @@ -206,7 +206,7 @@ class PercentStyleMultiline(logging.PercentStyle): 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) # type: ignore[attr-defined] + auto_indent = self._get_auto_indent(record.auto_indent) else: auto_indent = self._auto_indent @@ -295,6 +295,13 @@ def pytest_addoption(parser: Parser) -> None: 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", @@ -304,13 +311,13 @@ def pytest_addoption(parser: Parser) -> None: add_option_ini( "--log-file-format", dest="log_file_format", - default=DEFAULT_LOG_FORMAT, + default=None, help="Log format used by the logging module", ) add_option_ini( "--log-file-date-format", dest="log_file_date_format", - default=DEFAULT_LOG_DATE_FORMAT, + default=None, help="Log date format used by the logging module", ) add_option_ini( @@ -332,16 +339,16 @@ _HandlerType = TypeVar("_HandlerType", bound=logging.Handler) # Not using @contextmanager for performance reasons. -class catching_logs: +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: Optional[int] = None) -> None: + def __init__(self, handler: _HandlerType, level: int | None = None) -> None: self.handler = handler self.level = level - def __enter__(self): + def __enter__(self) -> _HandlerType: root_logger = logging.getLogger() if self.level is not None: self.handler.setLevel(self.level) @@ -351,7 +358,12 @@ class catching_logs: root_logger.setLevel(min(self.orig_level, self.level)) return self.handler - def __exit__(self, type, value, traceback): + 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) @@ -364,7 +376,7 @@ class LogCaptureHandler(logging_StreamHandler): def __init__(self) -> None: """Create a new log handler.""" super().__init__(StringIO()) - self.records: List[logging.LogRecord] = [] + 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.""" @@ -385,7 +397,7 @@ class LogCaptureHandler(logging_StreamHandler): # 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 + raise # noqa: PLE0704 @final @@ -395,10 +407,10 @@ class LogCaptureFixture: def __init__(self, item: nodes.Node, *, _ispytest: bool = False) -> None: check_ispytest(_ispytest) self._item = item - self._initial_handler_level: Optional[int] = None + self._initial_handler_level: int | None = None # Dict of log name -> log level. - self._initial_logger_levels: Dict[Optional[str], int] = {} - self._initial_disabled_logging_level: Optional[int] = None + self._initial_logger_levels: dict[str | None, int] = {} + self._initial_disabled_logging_level: int | None = None def _finalize(self) -> None: """Finalize the fixture. @@ -422,8 +434,8 @@ class LogCaptureFixture: return self._item.stash[caplog_handler_key] def get_records( - self, when: "Literal['setup', 'call', 'teardown']" - ) -> List[logging.LogRecord]: + self, when: Literal["setup", "call", "teardown"] + ) -> list[logging.LogRecord]: """Get the logging records for one of the possible test phases. :param when: @@ -442,12 +454,12 @@ class LogCaptureFixture: return _remove_ansi_escape_sequences(self.handler.stream.getvalue()) @property - def records(self) -> List[logging.LogRecord]: + 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]]: + 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. @@ -458,7 +470,7 @@ class LogCaptureFixture: return [(r.name, r.levelno, r.getMessage()) for r in self.records] @property - def messages(self) -> List[str]: + def messages(self) -> list[str]: """A list of format-interpolated log messages. Unlike 'records', which contains the format string and parameters for @@ -481,7 +493,7 @@ class LogCaptureFixture: self.handler.clear() def _force_enable_logging( - self, level: Union[int, str], logger_obj: logging.Logger + self, level: int | str, logger_obj: logging.Logger ) -> int: """Enable the desired logging level if the global level was disabled via ``logging.disabled``. @@ -497,7 +509,7 @@ class LogCaptureFixture: :return: The original disabled logging level. """ - original_disable_level: int = logger_obj.manager.disable # type: ignore[attr-defined] + original_disable_level: int = logger_obj.manager.disable if isinstance(level, str): # Try to translate the level string to an int for `logging.disable()` @@ -514,7 +526,7 @@ class LogCaptureFixture: return original_disable_level - def set_level(self, level: Union[int, str], logger: Optional[str] = None) -> None: + 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. @@ -523,7 +535,7 @@ class LogCaptureFixture: 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 :meth:`logging.disable`. + 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. @@ -540,14 +552,12 @@ class LogCaptureFixture: self._initial_disabled_logging_level = initial_disabled_logging_level @contextmanager - def at_level( - self, level: Union[int, str], logger: Optional[str] = None - ) -> Generator[None, None, None]: + 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 :meth:`logging.disable`. + 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. @@ -565,9 +575,25 @@ class LogCaptureFixture: 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, None, None]: +def caplog(request: FixtureRequest) -> Generator[LogCaptureFixture]: """Access and control log capturing. Captured logs are available through the following properties/methods:: @@ -583,7 +609,7 @@ def caplog(request: FixtureRequest) -> Generator[LogCaptureFixture, None, None]: result._finalize() -def get_log_level_for_setting(config: Config, *setting_names: str) -> Optional[int]: +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: @@ -600,9 +626,9 @@ def get_log_level_for_setting(config: Config, *setting_names: str) -> Optional[i except ValueError as e: # Python logging does not recognise this as a logging level raise UsageError( - "'{}' is not recognized as a logging level name for " - "'{}'. Please consider passing the " - "logging level num instead.".format(log_level, setting_name) + 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 @@ -636,14 +662,19 @@ class LoggingPlugin: self.report_handler.setFormatter(self.formatter) # File logging. - self.log_file_level = get_log_level_for_setting(config, "log_file_level") + 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_handler = _FileHandler(log_file, mode="w", encoding="UTF-8") + 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" @@ -664,9 +695,9 @@ class LoggingPlugin: 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: Union[ - _LiveLoggingStreamHandler, _LiveLoggingNullHandler - ] = _LiveLoggingStreamHandler(terminal_reporter, capture_manager) + self.log_cli_handler: ( + _LiveLoggingStreamHandler | _LiveLoggingNullHandler + ) = _LiveLoggingStreamHandler(terminal_reporter, capture_manager) else: self.log_cli_handler = _LiveLoggingNullHandler() log_cli_formatter = self._create_formatter( @@ -677,7 +708,7 @@ class LoggingPlugin: 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: + def _disable_loggers(self, loggers_to_disable: list[str]) -> None: if not loggers_to_disable: return @@ -720,12 +751,12 @@ class LoggingPlugin: fpath.parent.mkdir(exist_ok=True, parents=True) # https://github.com/python/mypy/issues/11193 - stream: io.TextIOWrapper = fpath.open(mode="w", encoding="UTF-8") # type: ignore[assignment] + 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): + def _log_cli_enabled(self) -> bool: """Return whether live logging is enabled.""" enabled = self._config.getoption( "--log-cli-level" @@ -740,35 +771,34 @@ class LoggingPlugin: return True - @hookimpl(hookwrapper=True, tryfirst=True) - def pytest_sessionstart(self) -> Generator[None, None, None]: + @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): - yield + return (yield) - @hookimpl(hookwrapper=True, tryfirst=True) - def pytest_collection(self) -> Generator[None, None, None]: + @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): - yield + return (yield) - @hookimpl(hookwrapper=True) - def pytest_runtestloop(self, session: Session) -> Generator[None, None, None]: + @hookimpl(wrapper=True) + def pytest_runtestloop(self, session: Session) -> Generator[None, object, object]: if session.config.option.collectonly: - yield - return + return (yield) - if self._log_cli_enabled() and self._config.getoption("verbose") < 1: + 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): - yield # Run all the tests. + return (yield) # Run all the tests. @hookimpl def pytest_runtest_logstart(self) -> None: @@ -779,58 +809,68 @@ class LoggingPlugin: def pytest_runtest_logreport(self) -> None: self.log_cli_handler.set_when("logreport") - def _runtest_for(self, item: nodes.Item, when: str) -> Generator[None, None, None]: + @contextmanager + 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: + 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 - yield + try: + yield + finally: + log = report_handler.stream.getvalue().strip() + item.add_report_section(when, "log", log) - log = report_handler.stream.getvalue().strip() - item.add_report_section(when, "log", log) - - @hookimpl(hookwrapper=True) - def pytest_runtest_setup(self, item: nodes.Item) -> Generator[None, None, None]: + @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]] = {} + empty: dict[str, list[logging.LogRecord]] = {} item.stash[caplog_records_key] = empty - yield from self._runtest_for(item, "setup") + with self._runtest_for(item, "setup"): + yield - @hookimpl(hookwrapper=True) - def pytest_runtest_call(self, item: nodes.Item) -> Generator[None, None, None]: + @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") + with self._runtest_for(item, "call"): + yield - @hookimpl(hookwrapper=True) - def pytest_runtest_teardown(self, item: nodes.Item) -> Generator[None, None, None]: + @hookimpl(wrapper=True) + def pytest_runtest_teardown(self, item: nodes.Item) -> Generator[None]: self.log_cli_handler.set_when("teardown") - yield from self._runtest_for(item, "teardown") - del item.stash[caplog_records_key] - del item.stash[caplog_handler_key] + try: + with self._runtest_for(item, "teardown"): + yield + 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(hookwrapper=True, tryfirst=True) - def pytest_sessionfinish(self) -> Generator[None, None, None]: + @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): - yield + return (yield) @hookimpl def pytest_unconfigure(self) -> None: @@ -863,7 +903,7 @@ class _LiveLoggingStreamHandler(logging_StreamHandler): def __init__( self, terminal_reporter: TerminalReporter, - capture_manager: Optional[CaptureManager], + capture_manager: CaptureManager | None, ) -> None: super().__init__(stream=terminal_reporter) # type: ignore[arg-type] self.capture_manager = capture_manager @@ -875,7 +915,7 @@ class _LiveLoggingStreamHandler(logging_StreamHandler): """Reset the handler; should be called before the start of each test.""" self._first_record_emitted = False - def set_when(self, when: Optional[str]) -> None: + 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 diff --git a/venv/lib/python3.10/site-packages/_pytest/main.py b/venv/lib/python3.10/site-packages/_pytest/main.py index ea89a63..9bc930d 100644 --- a/venv/lib/python3.10/site-packages/_pytest/main.py +++ b/venv/lib/python3.10/site-packages/_pytest/main.py @@ -1,79 +1,63 @@ """Core implementation of the testing process: init, session, runtest loop.""" + +from __future__ import annotations + import argparse +from collections.abc import Callable +from collections.abc import Iterable +from collections.abc import Iterator +from collections.abc import Sequence +from collections.abc import Set as AbstractSet import dataclasses import fnmatch import functools import importlib +import importlib.util import os -import sys from pathlib import Path -from typing import Callable -from typing import Dict -from typing import FrozenSet -from typing import Iterator -from typing import List -from typing import Optional -from typing import Sequence -from typing import Set -from typing import Tuple -from typing import Type +import sys +from typing import final +from typing import Literal +from typing import overload from typing import TYPE_CHECKING -from typing import Union +import warnings + +import pluggy -import _pytest._code from _pytest import nodes -from _pytest.compat import final -from _pytest.compat import overload +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 OverrideIniAction from _pytest.config.argparsing import Parser -from _pytest.fixtures import FixtureManager +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 visit +from _pytest.pathlib import samefile_nofollow +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 Literal + 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( + group = parser.getgroup("general") + group._addoption( # private to use reserved lower-case short option "-x", "--exitfirst", action="store_const", @@ -81,6 +65,60 @@ def pytest_addoption(parser: Parser) -> None: const=1, help="Exit instantly on first error or failed test", ) + 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=OverrideIniAction, + ini_option="strict_config", + ini_value="true", + help="Enables the strict_config option", + ) + group.addoption( + "--strict-markers", + action=OverrideIniAction, + ini_option="strict_markers", + ini_value="true", + help="Enables the strict_markers option", + ) + group.addoption( + "--strict", + action=OverrideIniAction, + ini_option="strict", + ini_value="true", + help="Enables the strict option", + ) + parser.addini( + "strict_config", + "Any warnings encountered while parsing the `pytest` section of the " + "configuration file raise errors", + type="bool", + # None => fallback to `strict`. + default=None, + ) + parser.addini( + "strict_markers", + "Markers not registered in the `markers` section of the configuration " + "file raise errors", + type="bool", + # None => fallback to `strict`. + default=None, + ) + parser.addini( + "strict", + "Enables all strictness options, currently: " + "strict_config, strict_markers, strict_xfail, strict_parametrization_ids", + type="bool", + default=False, + ) + group = parser.getgroup("pytest-warnings") group.addoption( "-W", @@ -95,56 +133,6 @@ def pytest_addoption(parser: Parser) -> None: "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( @@ -207,6 +195,13 @@ def pytest_addoption(parser: Parser) -> None: default=False, help="Don't ignore tests in a local virtualenv directory", ) + 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( "--import-mode", default="prepend", @@ -215,8 +210,60 @@ def pytest_addoption(parser: Parser) -> None: help="Prepend/append to sys.path when importing test modules and conftest " "files. Default: prepend.", ) + 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=[], + ) + parser.addini( + "collect_imported_tests", + "Whether to collect tests in imported modules outside `testpaths`", + type="bool", + default=True, + ) + 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( # private to use reserved lower-case short option + "-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( + "--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.addoption( "--basetemp", dest="basetemp", @@ -256,8 +303,8 @@ def validate_basetemp(path: str) -> str: def wrap_session( - config: Config, doit: Callable[[Config, "Session"], Optional[Union[int, ExitCode]]] -) -> Union[int, ExitCode]: + config: Config, doit: Callable[[Config, Session], int | ExitCode | None] +) -> int | ExitCode: """Skeleton command line program.""" session = Session.from_config(config) session.exitstatus = ExitCode.OK @@ -276,7 +323,7 @@ def wrap_session( session.exitstatus = ExitCode.TESTS_FAILED except (KeyboardInterrupt, exit.Exception): excinfo = _pytest._code.ExceptionInfo.from_current() - exitstatus: Union[int, ExitCode] = ExitCode.INTERRUPTED + exitstatus: int | ExitCode = ExitCode.INTERRUPTED if isinstance(excinfo.value, exit.Exception): if excinfo.value.returncode is not None: exitstatus = excinfo.value.returncode @@ -314,11 +361,11 @@ def wrap_session( return session.exitstatus -def pytest_cmdline_main(config: Config) -> Union[int, ExitCode]: +def pytest_cmdline_main(config: Config) -> int | ExitCode: return wrap_session(config, _main) -def _main(config: Config, session: "Session") -> Optional[Union[int, ExitCode]]: +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) @@ -331,15 +378,14 @@ def _main(config: Config, session: "Session") -> Optional[Union[int, ExitCode]]: return None -def pytest_collection(session: "Session") -> None: +def pytest_collection(session: Session) -> None: session.perform_collect() -def pytest_runtestloop(session: "Session") -> bool: +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 "") + f"{session.testsfailed} error{'s' if session.testsfailed != 1 else ''} during collection" ) if session.config.option.collectonly: @@ -357,27 +403,31 @@ def pytest_runtestloop(session: "Session") -> bool: 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 appropriate activate script.""" - bindir = path.joinpath("Scripts" if sys.platform.startswith("win") else "bin") + 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: - if not bindir.is_dir(): - return False + return ( + path.joinpath("pyvenv.cfg").is_file() + or path.joinpath("conda-meta", "history").is_file() + ) except OSError: return False - activates = ( - "activate", - "activate.csh", - "activate.fish", - "Activate", - "Activate.bat", - "Activate.ps1", - ) - return any(fname.name in activates for fname in bindir.iterdir()) -def pytest_ignore_collect(collection_path: Path, config: Config) -> Optional[bool]: +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, rootpath=config.rootpath + "collect_ignore", path=collection_path.parent ) ignore_paths = ignore_paths or [] excludeopt = config.getoption("ignore") @@ -388,7 +438,7 @@ def pytest_ignore_collect(collection_path: Path, config: Config) -> Optional[boo return True ignore_globs = config._getconftest_pathlist( - "collect_ignore_glob", path=collection_path.parent, rootpath=config.rootpath + "collect_ignore_glob", path=collection_path.parent ) ignore_globs = ignore_globs or [] excludeglobopt = config.getoption("ignore_glob") @@ -410,7 +460,13 @@ def pytest_ignore_collect(collection_path: Path, config: Config) -> Optional[boo return None -def pytest_collection_modifyitems(items: List[nodes.Item], config: Config) -> 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 @@ -429,11 +485,15 @@ def pytest_collection_modifyitems(items: List[nodes.Item], config: Config) -> No class FSHookProxy: - def __init__(self, pm: PytestPluginManager, remove_mods) -> None: + def __init__( + self, + pm: PytestPluginManager, + remove_mods: AbstractSet[object], + ) -> None: self.pm = pm self.remove_mods = remove_mods - def __getattr__(self, name: str): + 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 @@ -450,7 +510,7 @@ class Failed(Exception): @dataclasses.dataclass -class _bestrelpath_cache(Dict[Path, str]): +class _bestrelpath_cache(dict[Path, str]): __slots__ = ("path",) path: Path @@ -462,7 +522,59 @@ class _bestrelpath_cache(Dict[Path, str]): @final -class Session(nodes.FSCollector): +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. @@ -474,10 +586,11 @@ class Session(nodes.FSCollector): _setupstate: SetupState # Set on the session by fixtures.pytest_sessionstart. _fixturemanager: FixtureManager - exitstatus: Union[int, ExitCode] + exitstatus: int | ExitCode def __init__(self, config: Config) -> None: super().__init__( + name="", path=config.rootpath, fspath=None, parent=None, @@ -487,28 +600,68 @@ class Session(nodes.FSCollector): ) self.testsfailed = 0 self.testscollected = 0 - self.shouldstop: Union[bool, str] = False - self.shouldfail: Union[bool, str] = False + self._shouldstop: bool | str = False + self._shouldfail: bool | str = False self.trace = config.trace.root.get("collection") - self._initialpaths: FrozenSet[Path] = frozenset() + 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._bestrelpathcache: dict[Path, str] = _bestrelpath_cache(config.rootpath) self.config.pluginmanager.register(self, name="session") @classmethod - def from_config(cls, config: Config) -> "Session": + 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, - ) + return ( + f"<{self.__class__.__name__} {self.name} " + f"exitstatus=%r " + f"testsfailed={self.testsfailed} " + f"testscollected={self.testscollected}>" + ) % getattr(self, "exitstatus", "") + + @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: @@ -530,92 +683,100 @@ class Session(nodes.FSCollector): raise self.Interrupted(self.shouldstop) @hookimpl(tryfirst=True) - def pytest_runtest_logreport( - self, report: Union[TestReport, CollectReport] - ) -> None: + 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) + self.shouldfail = f"stopping after {self.testsfailed} failures" pytest_collectreport = pytest_runtest_logreport - def isinitpath(self, path: Union[str, "os.PathLike[str]"]) -> bool: + 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) - return path_ in self._initialpaths + if with_parents: + return path_ in self._initialpaths_with_parents + else: + return path_ in self._initialpaths - def gethookproxy(self, fspath: "os.PathLike[str]"): + 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, - self.config.getoption("importmode"), - rootpath=self.config.rootpath, - ) + 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 fspath. - from .config.compat import PathAwareHookProxy - - proxy = PathAwareHookProxy(FSHookProxy(pm, 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 _recurse(self, direntry: "os.DirEntry[str]") -> bool: - if direntry.name == "__pycache__": - return False - fspath = Path(direntry.path) - ihook = self.gethookproxy(fspath.parent) - if ihook.pytest_ignore_collect(collection_path=fspath, config=self.config): - return False - return True - - def _collectfile( - self, fspath: Path, handle_dupes: bool = True + def _collect_path( + self, + path: Path, + path_cache: dict[Path, Sequence[nodes.Collector]], ) -> Sequence[nodes.Collector]: - assert ( - fspath.is_file() - ), "{!r} is not a file (isdir={!r}, exists={!r}, islink={!r})".format( - fspath, fspath.is_dir(), fspath.exists(), fspath.is_symlink() - ) - ihook = self.gethookproxy(fspath) - if not self.isinitpath(fspath): - if ihook.pytest_ignore_collect(collection_path=fspath, config=self.config): - return () + """Create a Collector for the given path. - if handle_dupes: - keepduplicates = self.config.getoption("keepduplicates") - if not keepduplicates: - duplicate_paths = self.config.pluginmanager._duplicatepaths - if fspath in duplicate_paths: - return () - else: - duplicate_paths.add(fspath) + `path_cache` makes it so the same Collectors are returned for the same + path. + """ + if path in path_cache: + return path_cache[path] - return ihook.pytest_collect_file(file_path=fspath, parent=self) # type: ignore[no-any-return] + 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: Optional[Sequence[str]] = ..., genitems: "Literal[True]" = ... - ) -> Sequence[nodes.Item]: - ... + self, args: Sequence[str] | None = ..., genitems: Literal[True] = ... + ) -> Sequence[nodes.Item]: ... @overload - def perform_collect( # noqa: F811 - self, args: Optional[Sequence[str]] = ..., genitems: bool = ... - ) -> Sequence[Union[nodes.Item, nodes.Collector]]: - ... + def perform_collect( + self, args: Sequence[str] | None = ..., genitems: bool = ... + ) -> Sequence[nodes.Item | nodes.Collector]: ... - def perform_collect( # noqa: F811 - self, args: Optional[Sequence[str]] = None, genitems: bool = True - ) -> Sequence[Union[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 @@ -635,24 +796,44 @@ class Session(nodes.FSCollector): self.trace("perform_collect", self, args) self.trace.root.indent += 1 - self._notfound: List[Tuple[str, Sequence[nodes.Collector]]] = [] - self._initial_parts: List[Tuple[Path, List[str]]] = [] - self.items: List[nodes.Item] = [] - hook = self.config.hook - items: Sequence[Union[nodes.Item, nodes.Collector]] = self.items + self._notfound = [] + self._initial_parts = [] + self._collection_cache = {} + self.items = [] + items: Sequence[nodes.Item | nodes.Collector] = self.items + consider_namespace_packages: bool = self.config.getini( + "consider_namespace_packages" + ) try: - initialpaths: List[Path] = [] - for arg in args: - fspath, parts = resolve_collection_argument( + initialpaths: list[Path] = [] + initialpaths_with_parents: list[Path] = [] + + collection_args = [ + resolve_collection_argument( self.config.invocation_params.dir, arg, + i, as_pypath=self.config.option.pyargs, + consider_namespace_packages=consider_namespace_packages, ) - self._initial_parts.append((fspath, parts)) - initialpaths.append(fspath) + for i, arg in enumerate(args) + ] + + if not self.config.getoption("keepduplicates"): + # Normalize the collection arguments -- remove duplicates and overlaps. + self._initial_parts = normalize_collection_arguments(collection_args) + else: + self._initial_parts = collection_args + + for collection_argument in self._initial_parts: + 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 @@ -661,12 +842,13 @@ class Session(nodes.FSCollector): for arg, collectors in self._notfound: if collectors: errors.append( - f"not found: {arg}\n(no name {arg!r} in any of {collectors!r})" + 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: @@ -679,193 +861,225 @@ class Session(nodes.FSCollector): session=self, config=self.config, items=items ) finally: + self._notfound = [] + self._initial_parts = [] + self._collection_cache = {} hook.pytest_collection_finish(session=self) - self.testscollected = len(items) + if genitems: + self.testscollected = len(items) + return items - def collect(self) -> Iterator[Union[nodes.Item, nodes.Collector]]: - from _pytest.python import Package + 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 - # Keep track of any collected nodes in here, so we don't duplicate fixtures. - node_cache1: Dict[Path, Sequence[nodes.Collector]] = {} - node_cache2: Dict[Tuple[Type[nodes.Collector], Path], nodes.Collector] = {} + 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]] = {} - # Keep track of any collected collectors in matchnodes paths, so they - # are not collected more than once. - matchnodes_cache: Dict[Tuple[Type[nodes.Collector], str], CollectReport] = {} + pm = self.config.pluginmanager - # Directories of pkgs with dunder-init files. - pkg_roots: Dict[Path, Package] = {} - - for argpath, names in self._initial_parts: - self.trace("processing argument", (argpath, names)) + for collection_argument in self._initial_parts: + self.trace("processing argument", collection_argument) self.trace.root.indent += 1 - # Start with a Session root, and delve to argpath item (dir or file) - # and stack all Packages found on the way. - # No point in finding packages when collecting doctests. - if not self.config.getoption("doctestmodules", False): - pm = self.config.pluginmanager - for parent in (argpath, *argpath.parents): - if not pm._is_in_confcutdir(argpath): - break + argpath = collection_argument.path + names = collection_argument.parts + parametrization = collection_argument.parametrization + module_name = collection_argument.module_name - if parent.is_dir(): - pkginit = parent / "__init__.py" - if pkginit.is_file() and pkginit not in node_cache1: - col = self._collectfile(pkginit, handle_dupes=False) - if col: - if isinstance(col[0], Package): - pkg_roots[parent] = col[0] - node_cache1[col[0].path] = [col[0]] - - # If it's a directory argument, recurse and look for any Subpackages. - # Let the Package collector deal with subnodes, don't collect here. + # resolve_collection_argument() ensures this. if argpath.is_dir(): assert not names, f"invalid arg {(argpath, names)!r}" - seen_dirs: Set[Path] = set() - for direntry in visit(argpath, self._recurse): - if not direntry.is_file(): - continue - - path = Path(direntry.path) - dirpath = path.parent - - if dirpath not in seen_dirs: - # Collect packages first. - seen_dirs.add(dirpath) - pkginit = dirpath / "__init__.py" - if pkginit.exists(): - for x in self._collectfile(pkginit): - yield x - if isinstance(x, Package): - pkg_roots[dirpath] = x - if dirpath in pkg_roots: - # Do not collect packages here. - continue - - for x in self._collectfile(path): - key2 = (type(x), x.path) - if key2 in node_cache2: - yield node_cache2[key2] - else: - node_cache2[key2] = x - yield x + 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: - assert argpath.is_file() + # 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) - if argpath in node_cache1: - col = node_cache1[argpath] + # 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: - collect_root = pkg_roots.get(argpath.parent, self) - col = collect_root._collectfile(argpath, handle_dupes=False) - if col: - node_cache1[argpath] = col + # 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 - matching = [] - work: List[ - Tuple[Sequence[Union[nodes.Item, nodes.Collector]], Sequence[str]] - ] = [(col, names)] - while work: - self.trace("matchnodes", col, names) - self.trace.root.indent += 1 + # 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). But use a version + # which doesn't resolve symlinks, otherwise we might match the + # same file more than once (#12039). + is_match = samefile_nofollow(node.path, matchparts[0]) - matchnodes, matchnames = work.pop() - for node in matchnodes: - if not matchnames: - matching.append(node) - continue - if not isinstance(node, nodes.Collector): - continue - key = (type(node), node.nodeid) - if key in matchnodes_cache: - rep = matchnodes_cache[key] + # Name part e.g. `TestIt` in `/a/b/test_file.py::TestIt::test_it`. + else: + if len(matchparts) == 1: + # This the last part, one parametrization goes. + if parametrization is not None: + # A parametrized arg must match exactly. + is_match = node.name == matchparts[0] + parametrization + else: + # A non-parameterized arg matches all parametrizations (if any). + # TODO: Remove the hacky split once the collection structure + # contains parametrization. + is_match = node.name.split("[")[0] == matchparts[0] else: - rep = collect_one_node(node) - matchnodes_cache[key] = rep - if rep.passed: - submatchnodes = [] - for r in rep.result: - # TODO: Remove parametrized workaround once collection structure contains - # parametrization. - if ( - r.name == matchnames[0] - or r.name.split("[")[0] == matchnames[0] - ): - submatchnodes.append(r) - if submatchnodes: - work.append((submatchnodes, matchnames[1:])) - else: - # Report collection failures here to avoid failing to run some test - # specified in the command line because the module could not be - # imported (#134). - node.ihook.pytest_collectreport(report=rep) + is_match = node.name == matchparts[0] + if is_match: + work.append((node, matchparts[1:])) + any_matched_in_collector = True - self.trace("matchnodes finished -> ", len(matching), "nodes") - self.trace.root.indent -= 1 + if not any_matched_in_collector: + notfound_collectors.append(matchnode) - if not matching: - report_arg = "::".join((str(argpath), *names)) - self._notfound.append((report_arg, col)) - continue - - # If __init__.py was the only file requested, then the matched - # node will be the corresponding Package (by default), and the - # first yielded item will be the __init__ Module itself, so - # just use that. If this special case isn't taken, then all the - # files in the package will be yielded. - if argpath.name == "__init__.py" and isinstance(matching[0], Package): - try: - yield next(iter(matching[0].collect())) - except StopIteration: - # The package collects nothing with only an __init__.py - # file in it, which gets ignored by the default - # "python_files" option. - pass - continue - - yield from matching + 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: Union[nodes.Item, nodes.Collector] - ) -> Iterator[nodes.Item]: + 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) - rep = collect_one_node(node) + # For backward compat, dedup only applies to files. + handle_dupes = not isinstance(node, nodes.File) + rep, duplicate = self._collect_one_node(node, handle_dupes) if rep.passed: for subnode in rep.result: yield from self.genitems(subnode) - node.ihook.pytest_collectreport(report=rep) + if not duplicate: + node.ihook.pytest_collectreport(report=rep) -def search_pypath(module_name: str) -> str: - """Search sys.path for the given a dotted module name, and return its file system path.""" +def search_pypath( + module_name: str, *, consider_namespace_packages: bool = False +) -> 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 module_name - if spec is None or spec.origin is None or spec.origin == "namespace": - return module_name - elif spec.submodule_search_locations: - return os.path.dirname(spec.origin) - else: + return None + + if spec is None: + return None + + if ( + spec.submodule_search_locations is None + or len(spec.submodule_search_locations) == 0 + ): + # Must be a simple module. return spec.origin + if consider_namespace_packages: + # If submodule_search_locations is set, it's a package (regular or namespace). + # Typically there is a single entry, but documentation claims it can be empty too + # (e.g. if the package has no physical location). + return spec.submodule_search_locations[0] + + if spec.origin is None: + # This is only the case for namespace packages + return None + + return os.path.dirname(spec.origin) + + +@dataclasses.dataclass(frozen=True) +class CollectionArgument: + """A resolved collection argument.""" + + path: Path + parts: Sequence[str] + parametrization: str | None + module_name: str | None + original_index: int + def resolve_collection_argument( - invocation_path: Path, arg: str, *, as_pypath: bool = False -) -> Tuple[Path, List[str]]: + invocation_path: Path, + arg: str, + arg_index: int, + *, + as_pypath: bool = False, + consider_namespace_packages: 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 @@ -873,27 +1087,45 @@ def resolve_collection_argument( "pkg/tests/test_foo.py::TestClass::test_foo" - This function ensures the path exists, and returns a tuple: + This function ensures the path exists, and returns a resolved `CollectionArgument`: - (Path("/full/path/to/pkg/tests/test_foo.py"), ["TestClass", "test_foo"]) + 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" + "pkg.tests.test_foo::TestClass::test_foo[a,b]" In which case we search sys.path for a matching module, and then return the *path* to the - found module. + 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"], + parametrization="[a,b]", + 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("[") + base, squacket, rest = arg.partition("[") strpath, *parts = base.split("::") - if parts: - parts[-1] = f"{parts[-1]}{squacket}{rest}" + if squacket and not parts: + raise UsageError(f"path cannot contain [] parametrization: {arg}") + parametrization = f"{squacket}{rest}" if squacket else None + module_name = None if as_pypath: - strpath = search_pypath(strpath) + pyarg_strpath = search_pypath( + strpath, consider_namespace_packages=consider_namespace_packages + ) + if pyarg_strpath is not None: + module_name = strpath + strpath = pyarg_strpath fspath = invocation_path / strpath fspath = absolutepath(fspath) if not safe_exists(fspath): @@ -910,4 +1142,62 @@ def resolve_collection_argument( else "directory argument cannot contain :: selection parts: {arg}" ) raise UsageError(msg.format(arg=arg)) - return fspath, parts + return CollectionArgument( + path=fspath, + parts=parts, + parametrization=parametrization, + module_name=module_name, + original_index=arg_index, + ) + + +def is_collection_argument_subsumed_by( + arg: CollectionArgument, by: CollectionArgument +) -> bool: + """Check if `arg` is subsumed (contained) by `by`.""" + # First check path subsumption. + if by.path != arg.path: + # `by` subsumes `arg` if `by` is a parent directory of `arg` and has no + # parts (collects everything in that directory). + if not by.parts: + return arg.path.is_relative_to(by.path) + return False + # Paths are equal, check parts. + # For example: ("TestClass",) is a prefix of ("TestClass", "test_method"). + if len(by.parts) > len(arg.parts) or arg.parts[: len(by.parts)] != by.parts: + return False + # Paths and parts are equal, check parametrization. + # A `by` without parametrization (None) matches everything, e.g. + # `pytest x.py::test_it` matches `x.py::test_it[0]`. Otherwise must be + # exactly equal. + if by.parametrization is not None and by.parametrization != arg.parametrization: + return False + return True + + +def normalize_collection_arguments( + collection_args: Sequence[CollectionArgument], +) -> list[CollectionArgument]: + """Normalize collection arguments to eliminate overlapping paths and parts. + + Detects when collection arguments overlap in either paths or parts and only + keeps the shorter prefix, or the earliest argument if duplicate, preserving + order. The result is prefix-free. + """ + # A quadratic algorithm is not acceptable since large inputs are possible. + # So this uses an O(n*log(n)) algorithm which takes advantage of the + # property that after sorting, a collection argument will immediately + # precede collection arguments it subsumes. An O(n) algorithm is not worth + # it. + collection_args_sorted = sorted( + collection_args, + key=lambda arg: (arg.path, arg.parts, arg.parametrization or ""), + ) + normalized: list[CollectionArgument] = [] + last_kept = None + for arg in collection_args_sorted: + if last_kept is None or not is_collection_argument_subsumed_by(arg, last_kept): + normalized.append(arg) + last_kept = arg + normalized.sort(key=lambda arg: arg.original_index) + return normalized diff --git a/venv/lib/python3.10/site-packages/_pytest/mark/__init__.py b/venv/lib/python3.10/site-packages/_pytest/mark/__init__.py index de46b4c..841d781 100644 --- a/venv/lib/python3.10/site-packages/_pytest/mark/__init__.py +++ b/venv/lib/python3.10/site-packages/_pytest/mark/__init__.py @@ -1,16 +1,19 @@ """Generic mechanism for marking and selecting python functions.""" + +from __future__ import annotations + +import collections +from collections.abc import Collection +from collections.abc import Iterable +from collections.abc import Set as AbstractSet import dataclasses -from typing import AbstractSet -from typing import Collection -from typing import List -from typing import Optional from typing import TYPE_CHECKING -from typing import Union from .expression import Expression -from .expression import ParseError +from .structures import _HiddenParam from .structures import EMPTY_PARAMETERSET_OPTION from .structures import get_empty_parameterset_mark +from .structures import HIDDEN_PARAM from .structures import Mark from .structures import MARK_GEN from .structures import MarkDecorator @@ -20,14 +23,17 @@ 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__ = [ + "HIDDEN_PARAM", "MARK_GEN", "Mark", "MarkDecorator", @@ -37,13 +43,13 @@ __all__ = [ ] -old_mark_config_key = StashKey[Optional[Config]]() +old_mark_config_key = StashKey[Config | None]() def param( *values: object, - marks: Union[MarkDecorator, Collection[Union[MarkDecorator, Mark]]] = (), - id: Optional[str] = None, + marks: MarkDecorator | Collection[MarkDecorator | Mark] = (), + id: str | _HiddenParam | None = None, ) -> ParameterSet: """Specify a parameter in `pytest.mark.parametrize`_ calls or :ref:`parametrized fixtures `. @@ -61,22 +67,34 @@ def param( 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. + + :param marks: + A single mark or a list of marks to be applied to this parameter set. + + :ref:`pytest.mark.usefixtures ` cannot be added via this parameter. + + :type id: str | Literal[pytest.HIDDEN_PARAM] | None + :param id: + The id to attribute to this parameter set. + + .. versionadded:: 8.4 + :ref:`hidden-param` means to hide the parameter set + from the test name. Can only be used at most 1 time, as + test names need to be unique. """ return ParameterSet.param(*values, marks=marks, id=id) def pytest_addoption(parser: Parser) -> None: group = parser.getgroup("general") - group._addoption( + group._addoption( # private to use reserved lower-case short option "-k", action="store", dest="keyword", default="", metavar="EXPRESSION", help="Only run tests which match the given substring expression. " - "An expression is a Python evaluatable 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 " @@ -89,7 +107,7 @@ def pytest_addoption(parser: Parser) -> None: "The matching is case-insensitive.", ) - group._addoption( + group._addoption( # private to use reserved lower-case short option "-m", action="store", dest="markexpr", @@ -105,12 +123,12 @@ def pytest_addoption(parser: Parser) -> None: help="show markers (builtin, plugin and per-project ones).", ) - parser.addini("markers", "Markers for test functions", "linelist") + 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) -> Optional[Union[int, ExitCode]]: +def pytest_cmdline_main(config: Config) -> int | ExitCode | None: import _pytest.config if config.option.markers: @@ -120,7 +138,7 @@ def pytest_cmdline_main(config: Config) -> Optional[Union[int, ExitCode]]: parts = line.split(":", 1) name = parts[0] rest = parts[1] if len(parts) == 2 else "" - tw.write("@pytest.mark.%s:" % name, bold=True) + tw.write(f"@pytest.mark.{name}:", bold=True) tw.line(rest) tw.line() config._ensure_unconfigure() @@ -149,15 +167,22 @@ class KeywordMatcher: _names: AbstractSet[str] @classmethod - def from_item(cls, item: "Item") -> "KeywordMatcher": + def from_item(cls, item: Item) -> KeywordMatcher: mapped_names = set() - # Add the names of the current item and any parent items. + # 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 not isinstance(node, pytest.Session): - mapped_names.add(node.name) + 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()) @@ -172,17 +197,14 @@ class KeywordMatcher: return cls(mapped_names) - def __call__(self, subname: str) -> bool: + 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 + return any(subname in name.lower() for name in self._names) -def deselect_by_keyword(items: "List[Item]", config: Config) -> None: +def deselect_by_keyword(items: list[Item], config: Config) -> None: keywordexpr = config.option.keyword.lstrip() if not keywordexpr: return @@ -209,29 +231,37 @@ class MarkMatcher: Tries to match on any marker names, attached to the given colitem. """ - __slots__ = ("own_mark_names",) + __slots__ = ("own_mark_name_mapping",) - own_mark_names: AbstractSet[str] + own_mark_name_mapping: dict[str, list[Mark]] @classmethod - def from_item(cls, item: "Item") -> "MarkMatcher": - mark_names = {mark.name for mark in item.iter_markers()} - return cls(mark_names) + 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) -> bool: - return name in self.own_mark_names + 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: # pylint: disable=consider-using-any-or-all + 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: +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] = [] + remaining: list[Item] = [] + deselected: list[Item] = [] for item in items: - if expr.evaluate(MarkMatcher.from_item(item)): + if expr.evaluate(MarkMatcher.from_markers(item.iter_markers())): remaining.append(item) else: deselected.append(item) @@ -243,11 +273,13 @@ def deselect_by_mark(items: "List[Item]", config: Config) -> None: 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 + except SyntaxError as e: + raise UsageError( + f"{exc_message}: {e.text}: at column {e.offset}: {e.msg}" + ) from None -def pytest_collection_modifyitems(items: "List[Item]", config: Config) -> None: +def pytest_collection_modifyitems(items: list[Item], config: Config) -> None: deselect_by_keyword(items, config) deselect_by_mark(items, config) @@ -260,8 +292,8 @@ def pytest_configure(config: Config) -> None: if empty_parameterset not in ("skip", "xfail", "fail_at_collect", None, ""): raise UsageError( - "{!s} must be one of skip, xfail or fail_at_collect" - " but it is {!r}".format(EMPTY_PARAMETERSET_OPTION, empty_parameterset) + f"{EMPTY_PARAMETERSET_OPTION!s} must be one of skip, xfail or fail_at_collect" + f" but it is {empty_parameterset!r}" ) diff --git a/venv/lib/python3.10/site-packages/_pytest/mark/__pycache__/__init__.cpython-310.pyc b/venv/lib/python3.10/site-packages/_pytest/mark/__pycache__/__init__.cpython-310.pyc index 2e9ecf971d072a86700d1329e267874cbac9eb69..8b276915dd24c5f47f3a141a9605d62393081d26 100644 GIT binary patch literal 9643 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 IZg}W_0i*RFf&c&j delta 3964 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 O$gX@gAJ4|}>Hh<3lSr2U diff --git a/venv/lib/python3.10/site-packages/_pytest/mark/__pycache__/expression.cpython-310.pyc b/venv/lib/python3.10/site-packages/_pytest/mark/__pycache__/expression.cpython-310.pyc index f8ec9483f6cdd84872deb6b20e17eca75b2d2723..f4b171bf84e08518e9f0050a6caf037b12ab884f 100644 GIT binary patch literal 11449 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 U>^Pu_e9C$uuOA*X^5gpd0@^)`^8f$< literal 7371 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 diff --git a/venv/lib/python3.10/site-packages/_pytest/mark/__pycache__/structures.cpython-310.pyc b/venv/lib/python3.10/site-packages/_pytest/mark/__pycache__/structures.cpython-310.pyc index 29f15d8c0b25c116bf000483ab9510bd82f3eb2a..5d4b8b91f79d5e44bea177e090d3a42d13c973c2 100644 GIT binary patch literal 21132 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 QhesV7T(tPSojzp!Us?xtu>b%7 literal 19002 zcmb_^Ymi*Wb>7^^+=O$t08$quL2yZ67a*zE3IZ1d7kmjzngA(rEO~Y?eRna7 z*_mbESv*FwE=oXTM+t1pi4&(NIe?u?8A)7L6f3DXWyNt4|45~hANdj2&BLn5QOu;0 zRE4TU;xpfO`rdgg782viENl~O5X)l(%)-ge2Bw^MTD?Ur1;?Rt9MD|r^nI*4aV8Hu}yXG>X$rxDMUauW9t z&zJHN&mcZj8j^Sx@j|H}@f_mArD2KZ5igdC5+6c*q%_@%^R!65oUPJ*9gjK8^UjrF#)islD}&totQj;`{0c*6%CbC-MFD`_~^RJs|OW z>Ic^!EInwYf?fJ|(ciuGL+cNf9zxznN;Aq29$vB40d?PXxAcg*Kk(H9!B}v3I;DJd zaK%#(szb;*@^nf)q-L(C)J#yk=9FfG{eitUryf?1+^~>$SRFy$k!$uf>zY+M8ceC# zV0VyFbHT9{dpZ?7%1|A>VV53bJ=8d+4x+|E)+jxWb{_q@r5;0_V{zHXgNHG;q2TCr z3ZwX_`q*`^^ilQkAcrzXgU8t($B1v1aMdT&Co#^Csrle2Ms6YHQ|dTUJ|67)B}=cU zl=^i26YHNWebP#y_Y>*~^!`NfIOZ~s?*(-d-zVk!Q}}*Tox=Ai`96;Cr_>_87v=lY z_&%+k#`n|meFERlsAuv0tb9Ka>_^IT>UpF*FY{Xn7BIul+^|X~CGU({Lf%p^92^cj z&g7Z{C_JgGv#FUE-sB!xnz171WaBDc*;AEzy>h-DAf7+ftk;8TtJZ8V=h;@E;~W!j zH8s-R&(y*e-kCF%jg4Ak7183E%@#}cQu0fc^+285+>nN|FKn>UN}bI%F9o{ZtSH9M z2Cr=fjVe2L&IW8Xoz!v9UEN^%@VQrCTr8h@X7SYX&n`WUgbXuYscGbSa^p-zU;J!EHw+lurKSp^J;C}$>uPzU zq5%Scup9=h@;VAeo(@`@x>2>b2Jw$^7tbj?;T{C7R3~-KY7Vum4v3bruQ|%T=wRub zr6^l2H_&Lg+|C}aSJuy~%83~}%2wkgj52^grPY#ETG?z=Z>RJ@H2L;V&s}J)2XhxI z;rfxyFwk?CoBCq7QK<%Vr%x`PoVye>F3r_z=jSlp3(dx3v&W9kg|$|21Vt`ZR)cV^ zYydIGdCY|^y;;Rff^c@@YLo{$07mR@2A#dd)!1X{LwH4ARUjH=D|(e952Im%?5UvI z)D@sp6|}kg8$!&(BcS*-L^%C=YTep$tW?YHU@w2(-g39n%IR1gd(G*%o%D)3mD=(; zshe&qeaqU)bTZ1ll+q8jytPcnQ|TMlR<@Hpn?kyG(bkV4J&W{=%HFWMIXUD!&lb0G z-G1_0>5g+Vr}CX#C#{Bdr&jE#)Gh1FRx97hT*G^&u%st3N+3lp-Z)L&KDyd$`GNs{ z7Xy4?fPYXQ_6Z|y1pi>@SCACWA5=3=lv;}1@M3M_wnY@Fgv~~jzCwIEJ!9*m=pu6T zW+NJ5Qn}J9SIstUKe$Lt+tyF5;U78}9%B343jRU$CUI=Qsi?X6fji3V4%h6F6+!XHDhukG z;6XO94+AZ4+gr)jI)VZryD2yY+qP`W#baCEyZKDkDp>AN_J0>GH<$gP$1e-<53;U4 zi3(Lb1HU4^7Vv}@0JcD6hLZhqDsTcfNE>&Z6T_skTv5*PPHEP0fOXn``$?lEqVbt;V1&`ig7z@ z=dC>!_eXoYANaFU_;gf|H(40ASv0f)avhXgWa$HB+d+c_G5Za<=?PHqFk5Fj5IH)w zE`mH;9qSgqkhdy?{f2<5vW8u@c%`K))t0}ao9lk1;Wy8(0eF5>$FXoWpZDY6ybP*G z^DwmeZYIl|^y|TDrFs?MzFH3k3i_=Jpo;62t8ALD23A6rMrL(*pq)xX?NIcxhS0-h zZD3U^J~&$FH&;+lU%dNZ`ye+s*2RWo`O>{FZ39F6u;~LWRxx_8&CT_o(TYo$O))S` z)odJU`5=wH0#jYzXoeUa$9^81yzTC_~&9q&$HL&?Y52 zbDDhrI=*h&%DH7d1#xIA)p95nDfc2I03eaOVxLW^bT2;REp<0OyGSnlD^TzSTOYS<+y21zCm_Iy-;MAWKP<6JTdJ;FeiAj6of z1IdZ5GZ50y3%Frr1)f7BTtkpgO8rrwi}cW47zQH;Pmn zskU2q*WPdAm|%Y}iL@e14BOTuzFE_A3hx2TRuS<@mQUO7*q;6NcxLoR(l1pD9{6P& zx()O`@T2^-fG7MT1ikZxvPyEoP&UpE9?uh$({Xa(Spw&b0DDWm(>!Oa<$1Q?8MZVO z6eyviWNur|a8L+}flEyma`VSiIWOm_-KaMNZJ3Qyg4^p!rF*@of@u?HkZub&eT?AT z;jI+as2aPTDUGU;U{uNQ#fMu!G;Fv$YvG})K18Z@Akf?$o^n(OLkqT;qH6TRLchuP8uml-_$btY^ZSFwDd686^h`z-Phg7ySY zdb5MMQMw7TuLooi2ZgzGGCYL{2iLLIQWxzo%R?L(9+qzn$2BN-_CwmUAoWdkklLWS zh+q3VsVy4}kTp|eiEGa86qsacExlsZ*joB;SiivIAQ>a4nr{EQmqdr>SNukB+2>&W z%NJ_Z3w{kM9Urpcc_&*KK1F5!c#fRg8s4_?LCW?tz zKALl#JouCFD9~pfD!{+Uwccrub5gSd6=&?FC5X$?4M~h_Xc^JWgzzf8&eR5iC{r%0 zW);kbEJtrJ5I#b**0c{rL#ooM2rGhW68hnwuFPH(PQ?vr7@TJ8OAG`6f1a`4{3Iq! zha>uZJmGN!`IPHqol$$#-s`xS*whDh4ikIZ&Vmtxb>zW1@?bz7*he1h11#l-b}s*a ziW5agW(36>=1;}?EDtB=o0|<)MZ*I>Zh{3o;r9{j%uk5EF~x@li<|@coUF>h=8*}q z$_sL!?x+1fgEC$iv`0V)(h8k&I~RQl($AY*R!CdV?OO)=C5kQpXa3l%FZf{)ft4x^XyVX`#*&ADtcbCYk(H9Y-qBRjN1`+}NWyV8 zbq(whxNt|EL%==Ei(0hJ4bb~|M<-1UPLjBc0&>udkv1MaEQR2g=g^hLIS3;>4?`vd zx&X$r){aOo>P$k5@D!A-NT;!GFM&Rlkp|y%uo7sRLlX?x3B`pP#W=ph(xb5*YK61@ ztIbUz3^e^=RRuvQpFrG@QEhTq{xX{vh1Ekd%SK#~W9Vt!27Bp=&`VumjB=ubrz=aU%T+*fC zrja{@=VS=_7g2kMt|{5gCS6lh);o30j8CB{{Y!{!@0d;e#D1r?O*s}Ilb%If->GM2 zEOdLG90a^+Ht+TZR9^aC?Altr}J{WMUiK*a=s4DjVf zy>=0j0>~hD4{6CjD~F|-ct6SZgSi0~czBk8Y$PPeukol5K=0qL@;3(xv`hhTF6JQ# z*F7iu5!$V%FjoWFM;H@4rg#b&`E@1;V#yjyR!?XpkfL07yFW+J6N=;Bq}AR%FpJr4 zDVU`6YIp0Sv}}7+HJgp~71sQ=wIo~UwrvLSmyo_~Pw`|*u$9ZNpt@`<*WSM9Ns)o2 zCb40UTx$K78T<+Z&MYA)y-5$wSN~NMDxtFeRYV3_g(%rGM^FvWvGjqC9UkZaS*BA! zAKI{hI$N$$oXWZK0L&6lvxd_hS0BL15k`tN7z{!9VBJF8>+l?D^Ast_w)+&Od?%XB zYEDRO0XXJoz};v)L?o1DH>c@CC=%s``-;Kbto1J=6=?7}>-VPp0Lx};VcFE*0SZ5Z z9z)u?vvw9ou03KGAGKztwjHAMSJ|>)nH-?HC!r|2p_?#5wyv7vmF(``@k+)*;%oE) zRM(46Lvz5I#t40b0av6CHDshhbBWGF93u3 zQw}sj1u*)srr>k4uA7kqg{1!LsNG#?LYY*8cuMXNaxkP6Vt0D9V}+wwH2bEbV5Gcd zuToi%S2+G(kJSR#j5W$$1wwRe=-S7D4zNbGJeWHlfxk!wIw0`9*X~6;o5UMqtsFH; ztMJ6yN~3&Q$}e=%TTa92q|uMl^59njR1zbKTY+z2>V@+Z#sph78}!5h*&rFDxC%Qa zk0`M$29FYm{6_y8QzyvL_>Q#_)U}Edp&vfXgCh^*E+cfsZ6_jb%ZSk;A{g;RbQeHh zC^jgBhnraaBa#Ji2m?Ly(hHDf0DU9k_AP}FI&`-J0~q=fS!l;jRZRo&jY!?=*~lSK zl8fsVLC{@Mte=n|uIgDl12!hAgKok?!L>|mO!^i|(P;|00q8EE&vqc$8#B|kZSpyk zyn=Pm%ZRier29#-4zdAvG$cXa{|t)jGYo#6!EYdlvXm2H7gSVA06EdGOFLlXfyK~Z zP-D^iS#+X7dqSOtLr%5KVo<-_WC3Dxl&9RqeKGIJZp&V%1z>Hfq+$r|9;L`?b2@w(Q6wWt*_H?><1!Vm-s474y)9oJ# z&MYOMAbw+h;*UrKJOjv3aOS%!h$R+yo=s?-C*%_AywSr!wFbP?wct|1u9pcfoc9xe z0Xerdnw&KfFN9d%CU9mC^U0=c;^-N#oD6>6pm;d%pOn)~G90q{*i4QcZDt}K4`z%) z*W0dmiHu?@@Q|vgWX?;V^~4f`WI~od!_UhI!2P&1Qs2l_Wm3RF4A8%cB46c_lla3P zlrLbRCut#iSMsF#9W0*9dkT*@S#eQGF~i(*h;i{&>7QrZI_zJ^a(@lF; zDhOp03VZ}-&8|0_Xa_+^zkzCR5GrI1-pT2RM$@=>=5`wR!WtkrZWZ?5#sX=KN zdGtPlIYqEQ5TIK||9uqH{{TT3rHD?{{of6xGG=S`P>Ox^+R=Bzus`TxSUyIf;q552 zZQpLgs8RG24L=Eu-r|*NAddgW>>{Yv!|5KDeH%4?kKJ;J$^JIDMne!xK|dPAIQyNw zw3lS;AKBT_A8OXWJNErDaue(W4Un_{*TI|2-lu&$rUf2SGVt-u!d{-G$1N}kgd5le z+7OrW#Mc%jfuAn!j}%}#frl(m)4z*U$y(C?h*2Lw)j_}?-{ek2j#GHH(;Lqq z$HUAz%b?quX{@(OUCQ&XyLeG--*@+sC*)^ld^^_k&}5iBrWriRpvgcp7(xIu9Bzle z{|!Q%<~%gXo&IeGKb~0qLlpT}9OWz`KqC)-miL3iOQUGKG$8BX4Ecflz3mn3AK1gi zw>0Zk@eKU9#TM|qbuWS*3Ssg4xI1__EyEs`^W`vDsa=ta5d+H^Ws_vHN?euX$I0<~ zC$124*WX8WA9e1o$ahiCC{8S_$lW^c5}l>n-u5MVo-O})p#EbN`D1oOU}RHMmS)Ob zL8ZTgTmWN#`E;!gc^%G*G$(|E@W`x4X830U$3I68JAva*FiQPTkl8-?<2vubL;t~G zOFe@0&yevwHuq0if?sz`nCQEJC)_}g;Ks`kf$7Gh5AdphxblFF$<+X`FmdI{;Y}Bw zJl-DOU}GwC9E3vUAe`i3b{&G@)fJrdo)iQZx{%} zdokM6@S$$%tD@YVmvj02@&MeIy9cBrIDo{i!sgIHquqdkGq$_6a2!ewK(S_F@k)hu z%zld@CHAIn#TL+>Wz=8jYjZY)t&jf5aySx2=Kq6f(WoV87&?@+bI9+XB_`LvB-laM zQn{R%Fh~)=Um;`HR^nXXS(sy+9&J-^~bOp)b*?(@_Hc`eFv@ zu0BxOIQfVoxGN!`KRK3DN|15>5h6T}gCEf;Cf~y0TaMn)lq(lE9+sgmhyLr5rH|ol zK@WyIRXE5`!Ij3o3Hg|s^XU{~nK+idMNJrWW*(KJIKc1iwz14M)-r3^a3PN6^s~@% zS#Q`X`wbg+S@QHY#fXM*W9zf309{&M4I>|SU8od**VWL?f*R>qQ>oUl8l^u~tJuP~ z8mmFdQ{y*W38~4SXwSz0;Zc*;!+z}O9;=pnCd1VlbnxPU8tY#d0H5~5+%hmp(jMW9 z`zdY3jDqp!4Z-!3l%@tS~s&&c)Xb{X^~h4~g30u&;2#j6|Ghx+_? z4n5KSjih}4Har;mM@09>Q+LIOA5ilNziS@U!76kbG|9sA7S}O2LFfsgPPx?NT^3e~ zw+Nb{9ZkVQtyYIYJq!Z>LaVhA&d<%k?}nEHxf^GjdUY;n;MSlRGA=YPA89q^BH`?X z)_Ps;&>4E(COk%?;>koABbQzDzd*A7mkd6}pv2%D0vIEdg4?M;Gb$SCIoVMnEbIL& zmQA*FPBKom?Y`JhQ6NSh+0jz~`ZRy#Tc`Jc)!ybQuL5^q^3q z@~B+)xJN0m7pPIU^X{m%-z{2s7Yb^qS0IYKpM^{AC|q0DVsp(t%q!27(j4)_nT1Ke)5;J<7{@?SnH;wLG^dBPQPeI$l zDMYAncSo`qck;K#o=Qs@u{>2#X5dFv{{o(ntCjFWazk|w9ge`ccmWRXtT7~TAo14B);S_8k~!8#SdTm)PCemiCp)QsK8sj&l~@d z5Zl$IYkTBjr@`=V@bW)m&YD7^2sAz0I8p{V1!7pZ+{$XUd@iE8X}`8a1IDoSdhp`x@Yw!hcjvu#$`DyBk|79XjqgO-vX<9trx zk;ZUaLZ$|RtIL@!1Krqv@w+Ih|1*O@5XZi_rmv?kIFY=I)*S-^UpIyB05SnBBk>Ut zE=h0N5%NafH*A8hF%JHljP15#@JkTkKXFjsW3X*xaL5OLp{)px_RkS+Tux9RLhZfw z^frj~%9@70g2It|@v;gBMohTzA>|NHO+wCG;}b+++$P8GLDH7dMxJrANsO^rlfE#6A^(mM8 zZq+5Vu*6QdxxSM#4Q+2yEnv$W^0?ie+^OBWgM=i>c*650@HgW7zPpe2w<2Ou16;y? z+d2Wq7ophKcbF@Tki7B$@g0NF{{rmOgNFbvzM#|R^qj+;fqJvW zJ2ySf&$;S9VeowhJ&gM;4%2041SNn|6=e12EB!o*n>>_ z4+tJ3bRR$@3n3zJkEf@y7IH1g?3*_AB!#~ITM4l#J1!AS;BB8YNb4|e0EAxnFP z**6$G!r-?U{3ZjMxuW5uTcc9^ZDxFv!M7OfBuD-sGbkF!YVEY}QTDjGAa;TTB>Ytb zSx`;>k$wNfKNyz>|KQkRxlYl6i2>R=P%zxQ%D+E)k9~>v1NNVG9f$+V-g{tiA3%`C lO|JaA_5t45@=$Jk%o{FPaJ|prZw!AfJxB6S+s?h#{{>0l(?= (3, 8): - astNameConstant = ast.Constant -else: - astNameConstant = ast.NameConstant +from typing import overload +from typing import Protocol __all__ = [ "Expression", - "ParseError", + "ExpressionMatcher", ] +FILE_NAME: Final = "" + + class TokenType(enum.Enum): LPAREN = "left parenthesis" RPAREN = "right parenthesis" @@ -47,35 +56,24 @@ class TokenType(enum.Enum): NOT = "not" IDENT = "identifier" EOF = "end of input" + EQUAL = "=" + STRING = "string literal" + COMMA = "," @dataclasses.dataclass(frozen=True) class Token: - __slots__ = ("type", "value", "pos") + __slots__ = ("pos", "type", "value") 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") + __slots__ = ("current", "input", "tokens") def __init__(self, input: str) -> None: + self.input = input self.tokens = self.lex(input) self.current = next(self.tokens) @@ -90,6 +88,27 @@ class Scanner: 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 SyntaxError( + f'closing quote "{quote_char}" is missing', + (FILE_NAME, 1, pos + 1, input), + ) + value = input[pos : end_quote_pos + 1] + if (backslash_pos := input.find("\\")) != -1: + raise SyntaxError( + r'escaping with "\" not supported in marker expression', + (FILE_NAME, 1, backslash_pos + 1, input), + ) + yield Token(TokenType.STRING, value, pos) + pos += len(value) else: match = re.match(r"(:?\w|:|\+|-|\.|\[|\]|\\|/)+", input[pos:]) if match: @@ -104,13 +123,21 @@ class Scanner: yield Token(TokenType.IDENT, value, pos) pos += len(value) else: - raise ParseError( - pos + 1, + raise SyntaxError( f'unexpected character "{input[pos]}"', + (FILE_NAME, 1, pos + 1, input), ) yield Token(TokenType.EOF, "", pos) - def accept(self, type: TokenType, *, reject: bool = False) -> Optional[Token]: + @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: @@ -121,12 +148,12 @@ class Scanner: return None def reject(self, expected: Sequence[TokenType]) -> NoReturn: - raise ParseError( - self.current.pos + 1, + raise SyntaxError( "expected {}; got {}".format( " OR ".join(type.value for type in expected), self.current.type.value, ), + (FILE_NAME, 1, self.current.pos + 1, self.input), ) @@ -138,7 +165,7 @@ IDENT_PREFIX = "$" def expression(s: Scanner) -> ast.Expression: if s.accept(TokenType.EOF): - ret: ast.expr = astNameConstant(False) + ret: ast.expr = ast.Constant(False) else: ret = expr(s) s.accept(TokenType.EOF, reject=True) @@ -170,18 +197,108 @@ def not_expr(s: Scanner) -> ast.expr: return ret ident = s.accept(TokenType.IDENT) if ident: - return ast.Name(IDENT_PREFIX + ident.value, ast.Load()) + 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)) -class MatcherAdapter(Mapping[str, bool]): +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 SyntaxError( + f"not a valid python identifier {keyword_name.value}", + (FILE_NAME, 1, keyword_name.pos + 1, s.input), + ) + if keyword.iskeyword(keyword_name.value): + raise SyntaxError( + f"unexpected reserved python keyword `{keyword_name.value}`", + (FILE_NAME, 1, keyword_name.pos + 1, s.input), + ) + 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 SyntaxError( + f'unexpected character/s "{value_token.value}"', + (FILE_NAME, 1, value_token.pos + 1, s.input), + ) + + 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 ExpressionMatcher(Protocol): + """A callable which, given an identifier and optional kwargs, should return + whether it matches in an :class:`Expression` evaluation. + + Should be prepared to handle arbitrary strings as input. + + If no kwargs are provided, the expression of the form `foo`. + If kwargs are provided, the expression is of the form `foo(1, b=True, "s")`. + + If the expression is not supported (e.g. don't want to accept the kwargs + syntax variant), should raise :class:`~pytest.UsageError`. + + Example:: + + def matcher(name: str, /, **kwargs: str | int | bool | None) -> bool: + # Match `cat`. + if name == "cat" and not kwargs: + return True + # Match `dog(barks=True)`. + if name == "dog" and kwargs == {"barks": False}: + return True + return False + """ + + def __call__(self, name: str, /, **kwargs: str | int | bool | None) -> bool: ... + + +@dataclasses.dataclass +class MatcherNameAdapter: + matcher: ExpressionMatcher + 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: Callable[[str], bool]) -> None: + def __init__(self, matcher: ExpressionMatcher) -> None: self.matcher = matcher - def __getitem__(self, key: str) -> bool: - return self.matcher(key[len(IDENT_PREFIX) :]) + def __getitem__(self, key: str) -> MatcherNameAdapter: + return MatcherNameAdapter(matcher=self.matcher, name=key[len(IDENT_PREFIX) :]) def __iter__(self) -> Iterator[str]: raise NotImplementedError() @@ -190,39 +307,47 @@ class MatcherAdapter(Mapping[str, bool]): raise NotImplementedError() +@final class Expression: """A compiled match expression as used by -k and -m. The expression can be evaluated against different matchers. """ - __slots__ = ("code",) + __slots__ = ("_code", "input") - def __init__(self, code: types.CodeType) -> None: - self.code = code + def __init__(self, input: str, code: types.CodeType) -> None: + #: The original input line, as a string. + self.input: Final = input + self._code: Final = code @classmethod - def compile(self, input: str) -> "Expression": + def compile(cls, input: str) -> Expression: """Compile a match expression. :param input: The input expression - one line. + + :raises SyntaxError: If the expression is malformed. """ astexpr = expression(Scanner(input)) - code: types.CodeType = compile( + code = compile( astexpr, filename="", mode="eval", ) - return Expression(code) + return Expression(input, code) - def evaluate(self, matcher: Callable[[str], bool]) -> bool: + def evaluate(self, matcher: ExpressionMatcher) -> 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. + A callback which determines whether an identifier matches or not. + See the :class:`ExpressionMatcher` protocol for details and example. :returns: Whether the expression matches or not. + + :raises UsageError: + If the matcher doesn't support the expression. Cannot happen if the + matcher supports all expressions. """ - ret: bool = eval(self.code, {"__builtins__": {}}, MatcherAdapter(matcher)) - return ret + return bool(eval(self._code, {"__builtins__": {}}, MatcherAdapter(matcher))) diff --git a/venv/lib/python3.10/site-packages/_pytest/mark/structures.py b/venv/lib/python3.10/site-packages/_pytest/mark/structures.py index 55620f0..16bb6d8 100644 --- a/venv/lib/python3.10/site-packages/_pytest/mark/structures.py +++ b/venv/lib/python3.10/site-packages/_pytest/mark/structures.py @@ -1,36 +1,37 @@ +# mypy: allow-untyped-defs +from __future__ import annotations + import collections.abc +from collections.abc import Callable +from collections.abc import Collection +from collections.abc import Iterable +from collections.abc import Iterator +from collections.abc import Mapping +from collections.abc import MutableMapping +from collections.abc import Sequence import dataclasses +import enum import inspect -import warnings from typing import Any -from typing import Callable -from typing import Collection -from typing import Iterable -from typing import Iterator -from typing import List -from typing import Mapping -from typing import MutableMapping +from typing import final from typing import NamedTuple -from typing import Optional from typing import overload -from typing import Sequence -from typing import Set -from typing import Tuple -from typing import Type 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 final 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.raises import AbstractRaises +from _pytest.scope import _ScopeName from _pytest.warning_types import PytestUnknownMarkWarning + if TYPE_CHECKING: from ..nodes import Node @@ -38,33 +39,37 @@ if TYPE_CHECKING: EMPTY_PARAMETERSET_OPTION = "empty_parameter_set_mark" +# Singleton type for HIDDEN_PARAM, as described in: +# https://www.python.org/dev/peps/pep-0484/#support-for-singleton-types-in-unions +class _HiddenParam(enum.Enum): + token = 0 + + +#: Can be used as a parameter set id to hide it from the test name. +HIDDEN_PARAM = _HiddenParam.token + + def istestfunc(func) -> bool: return callable(func) and getattr(func, "__name__", "") != "" def get_empty_parameterset_mark( config: Config, argnames: Sequence[str], func -) -> "MarkDecorator": +) -> 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, - ) + argslisting = ", ".join(argnames) + _fs, lineno = getfslineno(func) + reason = f"got empty parameter set for ({argslisting})" 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) + f"Empty parameter set in '{func.__name__}' at line {lineno + 1}" ) else: raise LookupError(requested_mark) @@ -72,34 +77,68 @@ def get_empty_parameterset_mark( class ParameterSet(NamedTuple): - values: Sequence[Union[object, NotSetType]] - marks: Collection[Union["MarkDecorator", "Mark"]] - id: Optional[str] + """A set of values for a set of parameters along with associated marks and + an optional ID for the set. + + Examples:: + + pytest.param(1, 2, 3) + # ParameterSet(values=(1, 2, 3), marks=(), id=None) + + pytest.param("hello", id="greeting") + # ParameterSet(values=("hello",), marks=(), id="greeting") + + # Parameter set with marks + pytest.param(42, marks=pytest.mark.xfail) + # ParameterSet(values=(42,), marks=(MarkDecorator(...),), id=None) + + # From parametrize mark (parameter names + list of parameter sets) + pytest.mark.parametrize( + ("a", "b", "expected"), + [ + (1, 2, 3), + pytest.param(40, 2, 42, id="everything"), + ], + ) + # ParameterSet(values=(1, 2, 3), marks=(), id=None) + # ParameterSet(values=(40, 2, 42), marks=(), id="everything") + """ + + values: Sequence[object | NotSetType] + marks: Collection[MarkDecorator | Mark] + id: str | _HiddenParam | None @classmethod def param( cls, *values: object, - marks: Union["MarkDecorator", Collection[Union["MarkDecorator", "Mark"]]] = (), - id: Optional[str] = None, - ) -> "ParameterSet": + marks: MarkDecorator | Collection[MarkDecorator | Mark] = (), + id: str | _HiddenParam | None = None, + ) -> ParameterSet: if isinstance(marks, MarkDecorator): marks = (marks,) else: assert isinstance(marks, collections.abc.Collection) + if any(i.name == "usefixtures" for i in marks): + raise ValueError( + "pytest.param cannot add pytest.mark.usefixtures; see " + "https://docs.pytest.org/en/stable/reference/reference.html#pytest-param" + ) 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) + if not isinstance(id, str) and id is not HIDDEN_PARAM: + raise TypeError( + "Expected id to be a string or a `pytest.HIDDEN_PARAM` sentinel, " + f"got {type(id)}: {id!r}", + ) return cls(values, marks, id) @classmethod def extract_from( cls, - parameterset: Union["ParameterSet", Sequence[object], object], + parameterset: ParameterSet | Sequence[object] | object, force_tuple: bool = False, - ) -> "ParameterSet": + ) -> ParameterSet: """Extract from an object or objects. :param parameterset: @@ -110,7 +149,6 @@ class ParameterSet(NamedTuple): Enforce tuple wrapping so single argument tuple values don't get decomposed and break tests. """ - if isinstance(parameterset, cls): return parameterset if force_tuple: @@ -125,11 +163,11 @@ class ParameterSet(NamedTuple): @staticmethod def _parse_parametrize_args( - argnames: Union[str, Sequence[str]], - argvalues: Iterable[Union["ParameterSet", Sequence[object], object]], + argnames: str | Sequence[str], + argvalues: Iterable[ParameterSet | Sequence[object] | object], *args, **kwargs, - ) -> Tuple[Sequence[str], bool]: + ) -> tuple[Sequence[str], bool]: if isinstance(argnames, str): argnames = [x.strip() for x in argnames.split(",") if x.strip()] force_tuple = len(argnames) == 1 @@ -139,9 +177,9 @@ class ParameterSet(NamedTuple): @staticmethod def _parse_parametrize_parameters( - argvalues: Iterable[Union["ParameterSet", Sequence[object], object]], + argvalues: Iterable[ParameterSet | Sequence[object] | object], force_tuple: bool, - ) -> List["ParameterSet"]: + ) -> list[ParameterSet]: return [ ParameterSet.extract_from(x, force_tuple=force_tuple) for x in argvalues ] @@ -149,12 +187,12 @@ class ParameterSet(NamedTuple): @classmethod def _for_parametrize( cls, - argnames: Union[str, Sequence[str]], - argvalues: Iterable[Union["ParameterSet", Sequence[object], object]], + argnames: str | Sequence[str], + argvalues: Iterable[ParameterSet | Sequence[object] | object], func, config: Config, nodeid: str, - ) -> Tuple[Sequence[str], List["ParameterSet"]]: + ) -> tuple[Sequence[str], list[ParameterSet]]: argnames, force_tuple = cls._parse_parametrize_args(argnames, argvalues) parameters = cls._parse_parametrize_parameters(argvalues, force_tuple) del argvalues @@ -184,7 +222,9 @@ class ParameterSet(NamedTuple): # 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) + ParameterSet( + values=(NOTSET,) * len(argnames), marks=[mark], id="NOTSET" + ) ) return argnames, parameters @@ -197,24 +237,24 @@ class Mark: #: Name of the mark. name: str #: Positional arguments of the mark decorator. - args: Tuple[Any, ...] + args: tuple[Any, ...] #: Keyword arguments of the mark decorator. kwargs: Mapping[str, Any] #: Source Mark for ids with parametrize Marks. - _param_ids_from: Optional["Mark"] = dataclasses.field(default=None, repr=False) + _param_ids_from: Mark | None = dataclasses.field(default=None, repr=False) #: Resolved/generated ids with parametrize Marks. - _param_ids_generated: Optional[Sequence[str]] = dataclasses.field( + _param_ids_generated: Sequence[str] | None = dataclasses.field( default=None, repr=False ) def __init__( self, name: str, - args: Tuple[Any, ...], + args: tuple[Any, ...], kwargs: Mapping[str, Any], - param_ids_from: Optional["Mark"] = None, - param_ids_generated: Optional[Sequence[str]] = None, + param_ids_from: Mark | None = None, + param_ids_generated: Sequence[str] | None = None, *, _ispytest: bool = False, ) -> None: @@ -230,7 +270,7 @@ class Mark: def _has_param_ids(self) -> bool: return "ids" in self.kwargs or len(self.args) >= 4 - def combined_with(self, other: "Mark") -> "Mark": + def combined_with(self, other: Mark) -> Mark: """Return a new Mark which is a combination of this Mark and another Mark. @@ -242,7 +282,7 @@ class Mark: assert self.name == other.name # Remember source of ids with parametrize Marks. - param_ids_from: Optional[Mark] = None + param_ids_from: Mark | None = None if self.name == "parametrize": if other._has_param_ids(): param_ids_from = other @@ -261,7 +301,7 @@ class Mark: # 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]) +Markable = TypeVar("Markable", bound=Callable[..., object] | type) @dataclasses.dataclass @@ -270,8 +310,8 @@ class MarkDecorator: ``MarkDecorators`` are created with ``pytest.mark``:: - mark1 = pytest.mark.NAME # Simple MarkDecorator - mark2 = pytest.mark.NAME(name1=value) # Parametrized MarkDecorator + mark1 = pytest.mark.NAME # Simple MarkDecorator + mark2 = pytest.mark.NAME(name1=value) # Parametrized MarkDecorator and can then be applied as decorators to test functions:: @@ -313,7 +353,7 @@ class MarkDecorator: return self.mark.name @property - def args(self) -> Tuple[Any, ...]: + def args(self) -> tuple[Any, ...]: """Alias for mark.args.""" return self.mark.args @@ -327,7 +367,7 @@ class MarkDecorator: """:meta private:""" return self.name # for backward-compat (2.4.1 had this attr) - def with_args(self, *args: object, **kwargs: object) -> "MarkDecorator": + 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 @@ -340,11 +380,11 @@ class MarkDecorator: # 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[misc] + def __call__(self, arg: Markable) -> Markable: # type: ignore[overload-overlap] pass @overload - def __call__(self, *args: object, **kwargs: object) -> "MarkDecorator": + def __call__(self, *args: object, **kwargs: object) -> MarkDecorator: pass def __call__(self, *args: object, **kwargs: object): @@ -352,17 +392,22 @@ class 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) + # For staticmethods/classmethods, the marks are eventually fetched from the + # function object, not the descriptor, so unwrap. + unwrapped_func = func + if isinstance(func, staticmethod | classmethod): + unwrapped_func = func.__func__ + if len(args) == 1 and (istestfunc(unwrapped_func) or is_class): + store_mark(unwrapped_func, self.mark, stacklevel=3) return func return self.with_args(*args, **kwargs) def get_unpacked_marks( - obj: Union[object, type], + obj: object | type, *, consider_mro: bool = True, -) -> List[Mark]: +) -> 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 @@ -392,7 +437,7 @@ def get_unpacked_marks( def normalize_mark_list( - mark_list: Iterable[Union[Mark, MarkDecorator]] + mark_list: Iterable[Mark | MarkDecorator], ) -> Iterable[Mark]: """ Normalize an iterable of Mark or MarkDecorator objects into a list of marks @@ -404,16 +449,22 @@ def normalize_mark_list( for mark in mark_list: mark_obj = getattr(mark, "mark", mark) if not isinstance(mark_obj, Mark): - raise TypeError(f"got {repr(mark_obj)} instead of Mark") + raise TypeError(f"got {mark_obj!r} instead of Mark") yield mark_obj -def store_mark(obj, mark: Mark) -> None: +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] @@ -422,59 +473,52 @@ def store_mark(obj, mark: Mark) -> None: # Typing for builtin pytest marks. This is cheating; it gives builtin marks # special privilege, and breaks modularity. But practicality beats purity... if TYPE_CHECKING: - from _pytest.scope import _ScopeName class _SkipMarkDecorator(MarkDecorator): - @overload # type: ignore[override,misc,no-overload-impl] - def __call__(self, arg: Markable) -> Markable: - ... + @overload # type: ignore[override,no-overload-impl] + def __call__(self, arg: Markable) -> Markable: ... @overload - def __call__(self, reason: str = ...) -> "MarkDecorator": - ... + def __call__(self, reason: str = ...) -> MarkDecorator: ... class _SkipifMarkDecorator(MarkDecorator): def __call__( # type: ignore[override] self, - condition: Union[str, bool] = ..., - *conditions: Union[str, bool], + condition: str | bool = ..., + *conditions: str | bool, reason: str = ..., - ) -> MarkDecorator: - ... + ) -> MarkDecorator: ... class _XfailMarkDecorator(MarkDecorator): - @overload # type: ignore[override,misc,no-overload-impl] - def __call__(self, arg: Markable) -> Markable: - ... + @overload # type: ignore[override,no-overload-impl] + def __call__(self, arg: Markable) -> Markable: ... @overload def __call__( self, - condition: Union[str, bool] = ..., - *conditions: Union[str, bool], + condition: str | bool = False, + *conditions: str | bool, reason: str = ..., run: bool = ..., - raises: Union[Type[BaseException], Tuple[Type[BaseException], ...]] = ..., + raises: None + | type[BaseException] + | tuple[type[BaseException], ...] + | AbstractRaises[BaseException] = ..., strict: bool = ..., - ) -> MarkDecorator: - ... + ) -> MarkDecorator: ... class _ParametrizeMarkDecorator(MarkDecorator): def __call__( # type: ignore[override] self, - argnames: Union[str, Sequence[str]], - argvalues: Iterable[Union[ParameterSet, Sequence[object], object]], + argnames: str | Sequence[str], + argvalues: Iterable[ParameterSet | Sequence[object] | object], *, - indirect: Union[bool, Sequence[str]] = ..., - ids: Optional[ - Union[ - Iterable[Union[None, str, float, int, bool]], - Callable[[Any], Optional[object]], - ] - ] = ..., - scope: Optional[_ScopeName] = ..., - ) -> MarkDecorator: - ... + 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] @@ -494,9 +538,10 @@ class MarkGenerator: import pytest + @pytest.mark.slowtest def test_function(): - pass + pass applies a 'slowtest' :class:`Mark` on ``test_function``. """ @@ -512,8 +557,8 @@ class MarkGenerator: def __init__(self, *, _ispytest: bool = False) -> None: check_ispytest(_ispytest) - self._config: Optional[Config] = None - self._markers: Set[str] = set() + 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.""" @@ -535,21 +580,24 @@ class MarkGenerator: # 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'?") + strict_markers = self._config.getini("strict_markers") + if strict_markers is None: + strict_markers = self._config.getini("strict") + if strict_markers: + fail( + f"{name!r} not found in `markers` configuration option", + pytrace=False, + ) + warnings.warn( - "Unknown pytest.mark.%s - is this a typo? You can register " + 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" % name, + "https://docs.pytest.org/en/stable/how-to/mark.html", PytestUnknownMarkWarning, 2, ) @@ -562,9 +610,9 @@ MARK_GEN = MarkGenerator(_ispytest=True) @final class NodeKeywords(MutableMapping[str, Any]): - __slots__ = ("node", "parent", "_markers") + __slots__ = ("_markers", "node", "parent") - def __init__(self, node: "Node") -> None: + def __init__(self, node: Node) -> None: self.node = node self.parent = node.parent self._markers = {node.name: True} @@ -584,15 +632,13 @@ class NodeKeywords(MutableMapping[str, Any]): # 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 + return key in self._markers or ( + self.parent is not None and key in self.parent.keywords ) def update( # type: ignore[override] self, - other: Union[Mapping[str, Any], Iterable[Tuple[str, Any]]] = (), + other: Mapping[str, Any] | Iterable[tuple[str, Any]] = (), **kwds: Any, ) -> None: self._markers.update(other) diff --git a/venv/lib/python3.10/site-packages/_pytest/monkeypatch.py b/venv/lib/python3.10/site-packages/_pytest/monkeypatch.py index 9e51ff3..07cc3fc 100644 --- a/venv/lib/python3.10/site-packages/_pytest/monkeypatch.py +++ b/venv/lib/python3.10/site-packages/_pytest/monkeypatch.py @@ -1,24 +1,27 @@ +# mypy: allow-untyped-defs """Monkeypatching and mocking functionality.""" + +from __future__ import annotations + +from collections.abc import Generator +from collections.abc import Mapping +from collections.abc import MutableMapping +from contextlib import contextmanager import os +from pathlib import Path import re import sys -import warnings -from contextlib import contextmanager from typing import Any -from typing import Generator -from typing import List -from typing import Mapping -from typing import MutableMapping -from typing import Optional +from typing import final from typing import overload -from typing import Tuple from typing import TypeVar -from typing import Union +import warnings -from _pytest.compat import final +from _pytest.deprecated import MONKEYPATCH_LEGACY_NAMESPACE_PACKAGES from _pytest.fixtures import fixture from _pytest.warning_types import PytestWarning + RE_IMPORT_ERROR_NAME = re.compile(r"^No module named (.*)$") @@ -27,7 +30,7 @@ V = TypeVar("V") @fixture -def monkeypatch() -> Generator["MonkeyPatch", None, None]: +def monkeypatch() -> Generator[MonkeyPatch]: """A convenient fixture for monkey-patching. The fixture provides these methods to modify objects, dictionaries, or @@ -89,14 +92,12 @@ def annotated_getattr(obj: object, name: str, ann: str) -> object: obj = getattr(obj, name) except AttributeError as e: raise AttributeError( - "{!r} object at {} has no attribute {!r}".format( - type(obj).__name__, ann, name - ) + 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]: +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) @@ -129,14 +130,14 @@ class MonkeyPatch: """ def __init__(self) -> None: - self._setattr: List[Tuple[object, str, object]] = [] - self._setitem: List[Tuple[Mapping[Any, Any], object, object]] = [] - self._cwd: Optional[str] = None - self._savesyspath: Optional[List[str]] = 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", None, None]: + 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. @@ -168,8 +169,7 @@ class MonkeyPatch: name: object, value: Notset = ..., raising: bool = ..., - ) -> None: - ... + ) -> None: ... @overload def setattr( @@ -178,13 +178,12 @@ class MonkeyPatch: name: str, value: object, raising: bool = ..., - ) -> None: - ... + ) -> None: ... def setattr( self, - target: Union[str, object], - name: Union[object, str], + target: str | object, + name: object | str, value: object = notset, raising: bool = True, ) -> None: @@ -255,8 +254,8 @@ class MonkeyPatch: def delattr( self, - target: Union[object, str], - name: Union[str, Notset] = notset, + target: object | str, + name: str | Notset = notset, raising: bool = True, ) -> None: """Delete attribute ``name`` from ``target``. @@ -311,7 +310,7 @@ class MonkeyPatch: # 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: Optional[str] = None) -> None: + 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 @@ -321,10 +320,8 @@ class MonkeyPatch: if not isinstance(value, str): warnings.warn( # type: ignore[unreachable] PytestWarning( - "Value of environment variable {name} type should be str, but got " - "{value!r} (type: {type}); converted to str implicitly".format( - name=name, value=value, type=type(value).__name__ - ) + 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, ) @@ -344,7 +341,6 @@ class MonkeyPatch: 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)) @@ -352,8 +348,26 @@ class MonkeyPatch: # 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: + import pkg_resources from pkg_resources import fixup_namespace_packages + # Only issue deprecation warning if this call would actually have an + # effect for this specific path. + if ( + hasattr(pkg_resources, "_namespace_packages") + and pkg_resources._namespace_packages + ): + path_obj = Path(str(path)) + for ns_pkg in pkg_resources._namespace_packages: + if ns_pkg is None: + continue + ns_pkg_path = path_obj / ns_pkg.replace(".", os.sep) + if ns_pkg_path.is_dir(): + warnings.warn( + MONKEYPATCH_LEGACY_NAMESPACE_PACKAGES, stacklevel=2 + ) + break + fixup_namespace_packages(str(path)) # A call to syspathinsert() usually means that the caller wants to @@ -367,7 +381,7 @@ class MonkeyPatch: invalidate_caches() - def chdir(self, path: Union[str, "os.PathLike[str]"]) -> None: + def chdir(self, path: str | os.PathLike[str]) -> None: """Change the current working directory to the specified path. :param path: diff --git a/venv/lib/python3.10/site-packages/_pytest/nodes.py b/venv/lib/python3.10/site-packages/_pytest/nodes.py index 667a02b..6690f6a 100644 --- a/venv/lib/python3.10/site-packages/_pytest/nodes.py +++ b/venv/lib/python3.10/site-packages/_pytest/nodes.py @@ -1,47 +1,52 @@ +# mypy: allow-untyped-defs +from __future__ import annotations + +import abc +from collections.abc import Callable +from collections.abc import Iterable +from collections.abc import Iterator +from collections.abc import MutableMapping +from functools import cached_property +from functools import lru_cache import os -import warnings -from inspect import signature +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 List -from typing import MutableMapping -from typing import Optional +from typing import NoReturn from typing import overload -from typing import Set -from typing import Tuple -from typing import Type from typing import TYPE_CHECKING from typing import TypeVar -from typing import Union +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.compat import cached_property +from _pytest._code.code import TracebackStyle from _pytest.compat import LEGACY_PATH +from _pytest.compat import signature from _pytest.config import Config from _pytest.config import ConftestImportFailure -from _pytest.deprecated import FSCOLLECTOR_GETHOOKPROXY_ISINITPATH +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 - from _pytest._code.code import _TracebackStyle SEP = "/" @@ -49,63 +54,13 @@ SEP = "/" tracebackcutdir = Path(_pytest.__file__).parent -def iterparentnodeids(nodeid: str) -> Iterator[str]: - """Return the parent node IDs of a given node ID, inclusive. - - For the node ID - - "testing/code/test_excinfo.py::TestFormattedExcinfo::test_repr_source" - - the result would be - - "" - "testing" - "testing/code" - "testing/code/test_excinfo.py" - "testing/code/test_excinfo.py::TestFormattedExcinfo" - "testing/code/test_excinfo.py::TestFormattedExcinfo::test_repr_source" - - Note that / components are only considered until the first ::. - """ - pos = 0 - first_colons: Optional[int] = nodeid.find("::") - if first_colons == -1: - first_colons = None - # The root Session node - always present. - yield "" - # Eagerly consume SEP parts until first colons. - while True: - at = nodeid.find(SEP, pos, first_colons) - if at == -1: - break - if at > 0: - yield nodeid[:at] - pos = at + len(SEP) - # Eagerly consume :: parts. - while True: - at = nodeid.find("::", pos) - if at == -1: - break - if at > 0: - yield nodeid[:at] - pos = at + len("::") - # The node ID itself. - if nodeid: - yield nodeid - - -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" - ) +_T = TypeVar("_T") def _imply_path( - node_type: Type["Node"], - path: Optional[Path], - fspath: Optional[LEGACY_PATH], + node_type: type[Node], + path: Path | None, + fspath: LEGACY_PATH | None, ) -> Path: if fspath is not None: warnings.warn( @@ -126,37 +81,51 @@ def _imply_path( _NodeType = TypeVar("_NodeType", bound="Node") -class NodeMeta(type): - def __call__(self, *k, **kw): +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"{self.__module__}.{self.__name__}") + ).format(name=f"{cls.__module__}.{cls.__name__}") fail(msg, pytrace=False) - def _create(self, *k, **kw): + def _create(cls: type[_T], *k, **kw) -> _T: try: - return super().__call__(*k, **kw) + return super().__call__(*k, **kw) # type: ignore[no-any-return,misc] except TypeError: - sig = signature(getattr(self, "__init__")) + 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"{self} is not using a cooperative constructor and only takes {set(known_kw)}.\n" + 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) + return super().__call__(*k, **known_kw) # type: ignore[no-any-return,misc] -class Node(metaclass=NodeMeta): +class Node(abc.ABC, metaclass=NodeMeta): r"""Base class of :class:`Collector` and :class:`Item`, the components of the test collection tree. @@ -167,32 +136,32 @@ class Node(metaclass=NodeMeta): # 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. + #: :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", + "__dict__", "_nodeid", "_store", - "__dict__", + "config", + "name", + "parent", + "path", + "session", ) def __init__( self, name: str, - parent: "Optional[Node]" = None, - config: Optional[Config] = None, - session: "Optional[Session]" = None, - fspath: Optional[LEGACY_PATH] = None, - path: Optional[Path] = None, - nodeid: Optional[str] = None, + 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 @@ -219,17 +188,17 @@ class Node(metaclass=NodeMeta): 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: Path = _imply_path(type(self), path, fspath=fspath) + 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] = [] + self.own_markers: list[Mark] = [] #: Allow adding of extra keywords to use for matching. - self.extra_keyword_matches: Set[str] = set() + self.extra_keyword_matches: set[str] = set() if nodeid is not None: assert "::()" not in nodeid @@ -246,7 +215,7 @@ class Node(metaclass=NodeMeta): self._store = self.stash @classmethod - def from_parent(cls, parent: "Node", **kw): + def from_parent(cls, parent: Node, **kw) -> Self: """Public constructor for Nodes. This indirection got introduced in order to enable removing @@ -264,7 +233,7 @@ class Node(metaclass=NodeMeta): return cls._create(parent=parent, **kw) @property - def ihook(self): + def ihook(self) -> pluggy.HookRelay: """fspath-sensitive hook proxy used to call pytest hooks.""" return self.session.gethookproxy(self.path) @@ -295,9 +264,7 @@ class Node(metaclass=NodeMeta): # enforce type checks here to avoid getting a generic type error later otherwise. if not isinstance(warning, Warning): raise ValueError( - "warning must be an instance of Warning or subclass, got {!r}".format( - warning - ) + 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 @@ -324,23 +291,29 @@ class Node(metaclass=NodeMeta): def teardown(self) -> None: pass - def listchain(self) -> List["Node"]: - """Return list of all parent collectors up to self, starting from - the root of collection tree. + def iter_parents(self) -> Iterator[Node]: + """Iterate over all parent collectors starting from and including self + up to the root of the collection tree. - :returns: The nodes. + .. 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: Optional[Node] = self + item: Node | None = self while item is not None: chain.append(item) item = item.parent chain.reverse() return chain - def add_marker( - self, marker: Union[str, MarkDecorator], append: bool = True - ) -> None: + def add_marker(self, marker: str | MarkDecorator, append: bool = True) -> None: """Dynamically add a marker object to the node. :param marker: @@ -362,7 +335,7 @@ class Node(metaclass=NodeMeta): else: self.own_markers.insert(0, marker_.mark) - def iter_markers(self, name: Optional[str] = None) -> Iterator[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. @@ -371,29 +344,25 @@ class Node(metaclass=NodeMeta): return (x[1] for x in self.iter_markers_with_node(name=name)) def iter_markers_with_node( - self, name: Optional[str] = None - ) -> Iterator[Tuple["Node", Mark]]: + 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 reversed(self.listchain()): + 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) -> Optional[Mark]: - ... + 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) -> Mark: ... - def get_closest_marker( - self, name: str, default: Optional[Mark] = None - ) -> Optional[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). @@ -402,14 +371,14 @@ class Node(metaclass=NodeMeta): """ return next(self.iter_markers(name=name), default) - def listextrakeywords(self) -> Set[str]: + def listextrakeywords(self) -> set[str]: """Return a set of all extra keywords in self and any parents.""" - extra_keywords: Set[str] = set() + 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]: + def listnames(self) -> list[str]: return [x.name for x in self.listchain()] def addfinalizer(self, fin: Callable[[], object]) -> None: @@ -421,18 +390,17 @@ class Node(metaclass=NodeMeta): """ self.session._setupstate.addfinalizer(fin, self) - def getparent(self, cls: Type[_NodeType]) -> Optional[_NodeType]: - """Get the next parent node (including self) which is an instance of + 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. """ - current: Optional[Node] = self - while current and not isinstance(current, cls): - current = current.parent - assert current is None or isinstance(current, cls) - return current + 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 @@ -440,19 +408,19 @@ class Node(metaclass=NodeMeta): def _repr_failure_py( self, excinfo: ExceptionInfo[BaseException], - style: "Optional[_TracebackStyle]" = None, + style: TracebackStyle | None = None, ) -> TerminalRepr: from _pytest.fixtures import FixtureLookupError if isinstance(excinfo.value, ConftestImportFailure): - excinfo = ExceptionInfo.from_exc_info(excinfo.value.excinfo) + 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: Union[bool, Callable[[ExceptionInfo[BaseException]], Traceback]] + tbfilter: bool | Callable[[ExceptionInfo[BaseException]], Traceback] if self.config.getoption("fulltrace", False): style = "long" tbfilter = False @@ -467,11 +435,13 @@ class Node(metaclass=NodeMeta): else: style = "long" - if self.config.getoption("verbose", 0) > 1: + 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. @@ -490,13 +460,14 @@ class Node(metaclass=NodeMeta): style=style, tbfilter=tbfilter, truncate_locals=truncate_locals, + truncate_args=truncate_args, ) def repr_failure( self, excinfo: ExceptionInfo[BaseException], - style: "Optional[_TracebackStyle]" = None, - ) -> Union[str, TerminalRepr]: + style: TracebackStyle | None = None, + ) -> str | TerminalRepr: """Return a representation of a collection or test failure. .. seealso:: :ref:`non-python tests` @@ -506,26 +477,26 @@ class Node(metaclass=NodeMeta): return self._repr_failure_py(excinfo, style) -def get_fslocation_from_item(node: "Node") -> Tuple[Union[str, Path], Optional[int]]: +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. - * "fspath": just a path + * "path": just a path :rtype: A tuple of (str|Path, int) with filename and 0-based line number. """ # See Item.location. - location: Optional[Tuple[str, Optional[int], str]] = getattr(node, "location", None) + 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, "fspath", "unknown location"), -1 + return getattr(node, "path", "unknown location"), -1 -class Collector(Node): +class Collector(Node, abc.ABC): """Base class of all collectors. Collector create children through `collect()` and thus iteratively build @@ -535,14 +506,15 @@ class Collector(Node): class CollectError(Exception): """An error during collection, contains a custom message.""" - def collect(self) -> Iterable[Union["Item", "Collector"]]: + @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] - ) -> Union[str, TerminalRepr]: + ) -> str | TerminalRepr: """Return a representation of a collection failure. :param excinfo: Exception information for the failure. @@ -567,31 +539,37 @@ class Collector(Node): ntraceback = traceback.cut(path=self.path) if ntraceback == traceback: ntraceback = ntraceback.cut(excludepath=tracebackcutdir) - return excinfo.traceback.filter(excinfo) + return ntraceback.filter(excinfo) return excinfo.traceback -def _check_initialpaths_for_relpath(session: "Session", path: Path) -> Optional[str]: - 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 +@lru_cache(maxsize=1000) +def _check_initialpaths_for_relpath( + initial_paths: frozenset[Path], path: Path +) -> str | None: + if path in initial_paths: + return "" + + for parent in path.parents: + if parent in initial_paths: + return str(path.relative_to(parent)) + return None -class FSCollector(Collector): +class FSCollector(Collector, abc.ABC): """Base class for filesystem collectors.""" def __init__( self, - fspath: Optional[LEGACY_PATH] = None, - path_or_parent: Optional[Union[Path, Node]] = None, - path: Optional[Path] = None, - name: Optional[str] = None, - parent: Optional[Node] = None, - config: Optional[Config] = None, - session: Optional["Session"] = None, - nodeid: Optional[str] = None, + 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): @@ -622,7 +600,7 @@ class FSCollector(Collector): try: nodeid = str(self.path.relative_to(session.config.rootpath)) except ValueError: - nodeid = _check_initialpaths_for_relpath(session, path) + nodeid = _check_initialpaths_for_relpath(session._initialpaths, path) if nodeid and os.sep != SEP: nodeid = nodeid.replace(os.sep, SEP) @@ -641,30 +619,40 @@ class FSCollector(Collector): cls, parent, *, - fspath: Optional[LEGACY_PATH] = None, - path: Optional[Path] = None, + 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) - def gethookproxy(self, fspath: "os.PathLike[str]"): - warnings.warn(FSCOLLECTOR_GETHOOKPROXY_ISINITPATH, stacklevel=2) - return self.session.gethookproxy(fspath) - def isinitpath(self, path: Union[str, "os.PathLike[str]"]) -> bool: - warnings.warn(FSCOLLECTOR_GETHOOKPROXY_ISINITPATH, stacklevel=2) - return self.session.isinitpath(path) - - -class File(FSCollector): +class File(FSCollector, abc.ABC): """Base class for collecting tests from a file. :ref:`non-python tests`. """ -class Item(Node): +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. @@ -676,9 +664,9 @@ class Item(Node): self, name, parent=None, - config: Optional[Config] = None, - session: Optional["Session"] = None, - nodeid: Optional[str] = None, + config: Config | None = None, + session: Session | None = None, + nodeid: str | None = None, **kw, ) -> None: # The first two arguments are intentionally passed positionally, @@ -693,11 +681,11 @@ class Item(Node): nodeid=nodeid, **kw, ) - self._report_sections: List[Tuple[str, str, str]] = [] + 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.user_properties: list[tuple[str, object]] = [] self._check_item_and_collector_diamond_inheritance() @@ -730,6 +718,7 @@ class Item(Node): PytestWarning, ) + @abc.abstractmethod def runtest(self) -> None: """Run the test case for this item. @@ -756,7 +745,7 @@ class Item(Node): if content: self._report_sections.append((when, key, content)) - def reportinfo(self) -> Tuple[Union["os.PathLike[str]", str], Optional[int], str]: + 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: @@ -770,14 +759,14 @@ class Item(Node): return self.path, None, "" @cached_property - def location(self) -> Tuple[str, Optional[int], str]: + 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(os.fspath(location[0])) + 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/python3.10/site-packages/_pytest/nose.py b/venv/lib/python3.10/site-packages/_pytest/nose.py deleted file mode 100644 index 273bd04..0000000 --- a/venv/lib/python3.10/site-packages/_pytest/nose.py +++ /dev/null @@ -1,50 +0,0 @@ -"""Run testsuites written for nose.""" -import warnings - -from _pytest.config import hookimpl -from _pytest.deprecated import NOSE_SUPPORT -from _pytest.fixtures import getfixturemarker -from _pytest.nodes import Item -from _pytest.python import Function -from _pytest.unittest import TestCaseFunction - - -@hookimpl(trylast=True) -def pytest_runtest_setup(item: Item) -> None: - if not isinstance(item, Function): - return - # Don't do nose style setup/teardown on direct unittest style classes. - if isinstance(item, TestCaseFunction): - return - - # Capture the narrowed type of item for the teardown closure, - # see https://github.com/python/mypy/issues/2608 - func = item - - call_optional(func.obj, "setup", func.nodeid) - func.addfinalizer(lambda: call_optional(func.obj, "teardown", func.nodeid)) - - # NOTE: Module- and class-level fixtures are handled in python.py - # with `pluginmanager.has_plugin("nose")` checks. - # It would have been nicer to implement them outside of core, but - # it's not straightforward. - - -def call_optional(obj: object, name: str, nodeid: str) -> bool: - method = getattr(obj, name, None) - if method is None: - return False - is_fixture = getfixturemarker(method) is not None - if is_fixture: - return False - if not callable(method): - return False - # Warn about deprecation of this plugin. - method_name = getattr(method, "__name__", str(method)) - warnings.warn( - NOSE_SUPPORT.format(nodeid=nodeid, method=method_name, stage=name), stacklevel=2 - ) - # If there are any problems allow the exception to raise rather than - # silently ignoring it. - method() - return True diff --git a/venv/lib/python3.10/site-packages/_pytest/outcomes.py b/venv/lib/python3.10/site-packages/_pytest/outcomes.py index 1be97dd..766be95 100644 --- a/venv/lib/python3.10/site-packages/_pytest/outcomes.py +++ b/venv/lib/python3.10/site-packages/_pytest/outcomes.py @@ -1,35 +1,21 @@ """Exception classes and constants handling test outcomes as well as functions creating them.""" + +from __future__ import annotations + import sys -import warnings from typing import Any -from typing import Callable -from typing import cast +from typing import ClassVar from typing import NoReturn -from typing import Optional -from typing import Type -from typing import TypeVar -from _pytest.deprecated import KEYWORD_MSG_ARG - -TYPE_CHECKING = False # Avoid circular import through compat. - -if TYPE_CHECKING: - from typing_extensions import Protocol -else: - # typing.Protocol is only available starting from Python 3.8. It is also - # available from typing_extensions, but we don't want a runtime dependency - # on that. So use a dummy runtime implementation. - from typing import Generic - - Protocol = Generic +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: Optional[str] = None, pytrace: bool = True) -> None: + 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" @@ -58,7 +44,7 @@ class Skipped(OutcomeException): def __init__( self, - msg: Optional[str] = None, + msg: str | None = None, pytrace: bool = True, allow_module_level: bool = False, *, @@ -81,41 +67,18 @@ class Exit(Exception): """Raised for immediate program exits (no tracebacks/summaries).""" def __init__( - self, msg: str = "unknown reason", returncode: Optional[int] = None + 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 XFailed(Failed): + """Raised from an explicit call to pytest.xfail().""" -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: Optional[int] = None, *, msg: Optional[str] = None -) -> NoReturn: +class _Exit: """Exit testing process. :param reason: @@ -123,30 +86,24 @@ def exit( only because `msg` is deprecated. :param returncode: - Return code to be used when exiting pytest. + Return code to be used when exiting pytest. None means the same as ``0`` (no error), + same as :func:`sys.exit`. - :param msg: - Same as ``reason``, but deprecated. Will be removed in a future version, use ``reason`` instead. + :raises pytest.exit.Exception: + The exception that is raised. """ - __tracebackhide__ = True - from _pytest.config import UsageError - if reason and msg: - raise UsageError( - "cannot pass reason and msg to exit(), `msg` is deprecated, use `reason`." - ) - if not reason: - if msg is None: - raise UsageError("exit() requires a reason argument") - warnings.warn(KEYWORD_MSG_ARG.format(func="exit"), stacklevel=2) - reason = msg - raise Exit(reason, returncode) + Exception: ClassVar[type[Exit]] = Exit + + def __call__(self, reason: str = "", returncode: int | None = None) -> NoReturn: + __tracebackhide__ = True + raise Exit(msg=reason, returncode=returncode) -@_with_exception(Skipped) -def skip( - reason: str = "", *, allow_module_level: bool = False, msg: Optional[str] = None -) -> NoReturn: +exit: _Exit = _Exit() + + +class _Skip: """Skip an executing test with the given message. This function should be called only during testing (setup, call or teardown) or @@ -164,8 +121,8 @@ def skip( Defaults to False. - :param msg: - Same as ``reason``, but deprecated. Will be removed in a future version, use ``reason`` instead. + :raises pytest.skip.Exception: + The exception that is raised. .. note:: It is better to use the :ref:`pytest.mark.skipif ref` marker when @@ -174,13 +131,18 @@ def skip( Similarly, use the ``# doctest: +SKIP`` directive (see :py:data:`doctest.SKIP`) to skip a doctest statically. """ - __tracebackhide__ = True - reason = _resolve_msg_to_reason("skip", reason, msg) - raise Skipped(msg=reason, allow_module_level=allow_module_level) + + Exception: ClassVar[type[Skipped]] = Skipped + + def __call__(self, reason: str = "", allow_module_level: bool = False) -> NoReturn: + __tracebackhide__ = True + raise Skipped(msg=reason, allow_module_level=allow_module_level) -@_with_exception(Failed) -def fail(reason: str = "", pytrace: bool = True, msg: Optional[str] = None) -> NoReturn: +skip: _Skip = _Skip() + + +class _Fail: """Explicitly fail an executing test with the given message. :param reason: @@ -190,60 +152,28 @@ def fail(reason: str = "", pytrace: bool = True, msg: Optional[str] = None) -> N If False, msg represents the full failure information and no python traceback will be reported. - :param msg: - Same as ``reason``, but deprecated. Will be removed in a future version, use ``reason`` instead. + :raises pytest.fail.Exception: + The exception that is raised. """ - __tracebackhide__ = True - reason = _resolve_msg_to_reason("fail", reason, msg) - raise Failed(msg=reason, pytrace=pytrace) + + Exception: ClassVar[type[Failed]] = Failed + + def __call__(self, reason: str = "", pytrace: bool = True) -> NoReturn: + __tracebackhide__ = True + raise Failed(msg=reason, pytrace=pytrace) -def _resolve_msg_to_reason( - func_name: str, reason: str, msg: Optional[str] = None -) -> str: - """ - Handles converting the deprecated msg parameter if provided into - reason, raising a deprecation warning. This function will be removed - when the optional msg argument is removed from here in future. - - :param str func_name: - The name of the offending function, this is formatted into the deprecation message. - - :param str reason: - The reason= passed into either pytest.fail() or pytest.skip() - - :param str msg: - The msg= passed into either pytest.fail() or pytest.skip(). This will - be converted into reason if it is provided to allow pytest.skip(msg=) or - pytest.fail(msg=) to continue working in the interim period. - - :returns: - The value to use as reason. - - """ - __tracebackhide__ = True - if msg is not None: - if reason: - from pytest import UsageError - - raise UsageError( - f"Passing both ``reason`` and ``msg`` to pytest.{func_name}(...) is not permitted." - ) - warnings.warn(KEYWORD_MSG_ARG.format(func=func_name), stacklevel=3) - reason = msg - return reason +fail: _Fail = _Fail() -class XFailed(Failed): - """Raised from an explicit call to pytest.xfail().""" - - -@_with_exception(XFailed) -def xfail(reason: str = "") -> NoReturn: +class _XFail: """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. @@ -251,13 +181,27 @@ def xfail(reason: str = "") -> NoReturn: 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) + + Exception: ClassVar[type[XFailed]] = XFailed + + def __call__(self, reason: str = "") -> NoReturn: + __tracebackhide__ = True + raise XFailed(msg=reason) + + +xfail: _XFail = _XFail() def importorskip( - modname: str, minversion: Optional[str] = None, reason: Optional[str] = None + 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. @@ -270,30 +214,84 @@ def importorskip( :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 ImportError as exc: + 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}" - raise Skipped(reason, allow_module_level=True) from None + 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 @@ -304,8 +302,7 @@ def importorskip( if verattr is None or Version(verattr) < Version(minversion): raise Skipped( - "module %r has __version__ %r, required is: %r" - % (modname, verattr, minversion), + f"module {modname!r} has __version__ {verattr!r}, required is: {minversion!r}", allow_module_level=True, ) return mod diff --git a/venv/lib/python3.10/site-packages/_pytest/pastebin.py b/venv/lib/python3.10/site-packages/_pytest/pastebin.py index 22c7a62..c7b39d9 100644 --- a/venv/lib/python3.10/site-packages/_pytest/pastebin.py +++ b/venv/lib/python3.10/site-packages/_pytest/pastebin.py @@ -1,15 +1,18 @@ +# mypy: allow-untyped-defs """Submit failure or test session information to a pastebin service.""" -import tempfile -from io import StringIO -from typing import IO -from typing import Union -import pytest +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]]() @@ -17,7 +20,7 @@ pastebinfile_key = StashKey[IO[bytes]]() def pytest_addoption(parser: Parser) -> None: group = parser.getgroup("terminal reporting") - group._addoption( + group.addoption( "--pastebin", metavar="mode", action="store", @@ -63,18 +66,19 @@ def pytest_unconfigure(config: Config) -> None: # Write summary. tr.write_sep("=", "Sending information to Paste Service") pastebinurl = create_new_paste(sessionlog) - tr.write_line("pastebin session-log: %s\n" % pastebinurl) + tr.write_line(f"pastebin session-log: {pastebinurl}\n") -def create_new_paste(contents: Union[str, bytes]) -> str: +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.request import urlopen + from urllib.error import HTTPError from urllib.parse import urlencode + from urllib.request import urlopen params = {"code": contents, "lexer": "text", "expiry": "1week"} url = "https://bpa.st" @@ -82,8 +86,11 @@ def create_new_paste(contents: Union[str, bytes]) -> str: response: str = ( urlopen(url, data=urlencode(params).encode("ascii")).read().decode("utf-8") ) - except OSError as exc_info: # urllib errors - return "bad response: %s" % exc_info + except HTTPError as e: + with e: # HTTPErrors are also http responses that must be closed! + return f"bad response: {e}" + except OSError as e: # eg urllib.error.URLError + return f"bad response: {e}" m = re.search(r'href="/raw/(\w+)"', response) if m: return f"{url}/show/{m.group(1)}" diff --git a/venv/lib/python3.10/site-packages/_pytest/pathlib.py b/venv/lib/python3.10/site-packages/_pytest/pathlib.py index c2f8535..cd15434 100644 --- a/venv/lib/python3.10/site-packages/_pytest/pathlib.py +++ b/venv/lib/python3.10/site-packages/_pytest/pathlib.py @@ -1,20 +1,22 @@ +from __future__ import annotations + import atexit +from collections.abc import Callable +from collections.abc import Iterable +from collections.abc import Iterator import contextlib -import fnmatch -import importlib.util -import itertools -import os -import shutil -import sys -import types -import uuid -import warnings 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 @@ -22,25 +24,26 @@ 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 Callable -from typing import Dict -from typing import Iterable -from typing import Iterator -from typing import List -from typing import Optional -from typing import Set -from typing import Tuple -from typing import Type +from typing import Any from typing import TypeVar -from typing import Union +import uuid +import warnings from _pytest.compat import assert_never from _pytest.outcomes import skip from _pytest.warning_types import PytestWarning -LOCK_TIMEOUT = 60 * 60 * 24 * 3 +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) @@ -56,7 +59,7 @@ _IGNORED_WINERRORS = ( ) -def _ignore_error(exception): +def _ignore_error(exception: Exception) -> bool: return ( getattr(exception, "errno", None) in _IGNORED_ERRORS or getattr(exception, "winerror", None) in _IGNORED_WINERRORS @@ -68,12 +71,10 @@ def get_lock_path(path: _AnyPurePath) -> _AnyPurePath: def on_rm_rf_error( - func, + func: Callable[..., Any] | None, path: str, - excinfo: Union[ - BaseException, - Tuple[Type[BaseException], BaseException, Optional[types.TracebackType]], - ], + excinfo: BaseException + | tuple[type[BaseException], BaseException, types.TracebackType | None], *, start_path: Path, ) -> bool: @@ -101,9 +102,7 @@ def on_rm_rf_error( if func not in (os.open,): warnings.warn( PytestWarning( - "(rm_rf) unknown function {} when removing {}:\n{}: {}".format( - func, path, type(exc), exc - ) + f"(rm_rf) unknown function {func} when removing {path}:\n{type(exc)}: {exc}" ) ) return False @@ -171,23 +170,23 @@ def rm_rf(path: Path) -> None: shutil.rmtree(str(path), onerror=onerror) -def find_prefixed(root: Path, prefix: str) -> Iterator[Path]: - """Find all elements in root that begin with the prefix, case insensitive.""" +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 root.iterdir(): + for x in os.scandir(root): if x.name.lower().startswith(l_prefix): yield x -def extract_suffixes(iter: Iterable[PurePath], prefix: str) -> Iterator[str]: +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 p in iter: - yield p.name[p_len:] + for entry in iter: + yield entry.name[p_len:] def find_suffixes(root: Path, prefix: str) -> Iterator[str]: @@ -195,7 +194,7 @@ def find_suffixes(root: Path, prefix: str) -> Iterator[str]: return extract_suffixes(find_prefixed(root, prefix), prefix) -def parse_num(maybe_num) -> int: +def parse_num(maybe_num: str) -> int: """Parse number path suffixes, returns -1 on error.""" try: return int(maybe_num) @@ -203,9 +202,7 @@ def parse_num(maybe_num) -> int: return -1 -def _force_symlink( - root: Path, target: Union[str, PurePath], link_to: Union[str, Path] -) -> None: +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 @@ -242,7 +239,7 @@ def make_numbered_dir(root: Path, prefix: str, mode: int = 0o700) -> Path: else: raise OSError( "could not create numbered dir with prefix " - "{prefix} in {root} after 10 tries".format(prefix=prefix, root=root) + f"{prefix} in {root} after 10 tries" ) @@ -263,7 +260,9 @@ def create_cleanup_lock(p: Path) -> Path: return lock_path -def register_cleanup_lock_removal(lock_path: Path, register=atexit.register): +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() @@ -346,15 +345,15 @@ 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 - paths = find_prefixed(root, prefix) - paths, paths2 = itertools.tee(paths) - numbers = map(parse_num, extract_suffixes(paths2, prefix)) - for path, number in zip(paths, numbers): + 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, strict=True): if number <= max_delete: - yield path + yield Path(entry) -def cleanup_dead_symlinks(root: Path): +def cleanup_dead_symlinks(root: Path) -> None: for left_dir in root.iterdir(): if left_dir.is_symlink(): if not left_dir.resolve().exists(): @@ -417,7 +416,7 @@ def resolve_from_str(input: str, rootpath: Path) -> Path: return rootpath.joinpath(input) -def fnmatch_ex(pattern: str, path: Union[str, "os.PathLike[str]"]) -> bool: +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 @@ -453,15 +452,19 @@ def fnmatch_ex(pattern: str, path: Union[str, "os.PathLike[str]"]) -> bool: return fnmatch.fnmatch(name, pattern) -def parts(s: str) -> Set[str]: +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, dst, **kwargs): +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(str(src), str(dst), **kwargs) + os.symlink(src, dst, **kwargs) except OSError as e: skip(f"symlinks not supported: {e}") @@ -484,73 +487,90 @@ class ImportPathMismatchError(ImportError): def import_path( - p: Union[str, "os.PathLike[str]"], + path: str | os.PathLike[str], *, - mode: Union[str, ImportMode] = ImportMode.prepend, + 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 + """ + Import and return a module from the given path, which can be a file (a module) or a directory (a package). - The import mechanism used is controlled by the `mode` parameter: + :param path: + Path to the file to import. - * `mode == 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`. + :param mode: + Controls the underlying import mechanism that will be used: - * `mode == ImportMode.append`: same as `prepend`, but the directory will be appended - to the end of `sys.path`, if not already in `sys.path`. + * 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`. - * `mode == 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. + * 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) - path = Path(p) - 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] - for meta_importer in sys.meta_path: - spec = meta_importer.find_spec(module_name, [str(path.parent)]) - if spec is not None: - break - else: - spec = importlib.util.spec_from_file_location(module_name, str(path)) - - if spec is None: + 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}") - mod = importlib.util.module_from_spec(spec) - sys.modules[module_name] = mod - spec.loader.exec_module(mod) # type: ignore[union-attr] - insert_missing_modules(sys.modules, module_name) return mod - pkg_path = resolve_package_path(path) - if pkg_path is not None: - pkg_root = pkg_path.parent - names = list(path.with_suffix("").relative_to(pkg_root).parts) - if names[-1] == "__init__": - names.pop() - module_name = ".".join(names) - else: - pkg_root = path.parent - module_name = path.stem + 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 @@ -592,6 +612,148 @@ def import_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()) # type: ignore[arg-type] + + 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"): @@ -628,10 +790,15 @@ def module_name_from_path(path: Path, root: Path) -> str: 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: +def insert_missing_modules(modules: dict[str, ModuleType], module_name: str) -> None: """ Used by ``import_path`` to create intermediate modules when using mode=importlib. @@ -640,48 +807,45 @@ def insert_missing_modules(modules: Dict[str, ModuleType], module_name: str) -> otherwise "src.tests.test_foo" is not importable by ``__import__``. """ module_parts = module_name.split(".") - child_module: Union[ModuleType, None] = None - module: Union[ModuleType, None] = None - child_name: str = "" while module_name: - if module_name not in modules: - 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 - module = importlib.import_module(module_name) - except ModuleNotFoundError: - module = ModuleType( - module_name, - doc="Empty module created by pytest's importmode=importlib.", - ) - else: - module = modules[module_name] - if child_module: + 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(module, child_name): - setattr(module, child_name, child_module) - modules[module_name] = module - # Keep track of the child module while moving up the tree. - child_module, child_name = module, module_name.rpartition(".")[-1] + 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) -> Optional[Path]: +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 can not be determined. + Returns None if it cannot be determined. """ result = None for parent in itertools.chain((path,), path.parents): if parent.is_dir(): - if not parent.joinpath("__init__.py").is_file(): + if not (parent / "__init__.py").is_file(): break if not parent.name.isidentifier(): break @@ -689,30 +853,135 @@ def resolve_package_path(path: Path) -> Optional[Path]: return result -def scandir(path: Union[str, "os.PathLike[str]"]) -> List["os.DirEntry[str]"]: +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. + 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 = [] - with os.scandir(path) as s: - # Skip entries with symlink loops and other brokenness, so the caller - # doesn't have to deal with it. + # 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=lambda entry: entry.name) + entries.sort(key=sort_key) # type: ignore[arg-type] return entries def visit( - path: Union[str, "os.PathLike[str]"], recurse: Callable[["os.DirEntry[str]"], bool] -) -> Iterator["os.DirEntry[str]"]: + 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. @@ -726,16 +995,16 @@ def visit( yield from visit(entry.path, recurse) -def absolutepath(path: Union[Path, str]) -> Path: +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(str(path))) + return Path(os.path.abspath(path)) -def commonpath(path1: Path, path2: Path) -> Optional[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. @@ -776,24 +1045,6 @@ def bestrelpath(directory: Path, dest: Path) -> str: ) -# Originates from py. path.local.copy(), with siginficant trims and adjustments. -# TODO(py38): Replace with shutil.copytree(..., symlinks=True, dirs_exist_ok=True) -def copytree(source: Path, target: Path) -> None: - """Recursively copy a source directory to target.""" - assert source.is_dir() - for entry in visit(source, recurse=lambda entry: not entry.is_symlink()): - x = Path(entry) - relpath = x.relative_to(source) - newx = target / relpath - newx.parent.mkdir(exist_ok=True) - if x.is_symlink(): - newx.symlink_to(os.readlink(x)) - elif x.is_file(): - shutil.copyfile(x, newx) - elif x.is_dir(): - newx.mkdir(exist_ok=True) - - def safe_exists(p: Path) -> bool: """Like Path.exists(), but account for input arguments that might be too long (#11394).""" try: @@ -802,3 +1053,11 @@ def safe_exists(p: Path) -> bool: # ValueError: stat: path too long for Windows # OSError: [WinError 123] The filename, directory name, or volume label syntax is incorrect return False + + +def samefile_nofollow(p1: Path, p2: Path) -> bool: + """Test whether two paths reference the same actual file or directory. + + Unlike Path.samefile(), does not resolve symlinks. + """ + return os.path.samestat(p1.lstat(), p2.lstat()) diff --git a/venv/lib/python3.10/site-packages/_pytest/pytester.py b/venv/lib/python3.10/site-packages/_pytest/pytester.py index cdfc2c0..1cd5f05 100644 --- a/venv/lib/python3.10/site-packages/_pytest/pytester.py +++ b/venv/lib/python3.10/site-packages/_pytest/pytester.py @@ -1,37 +1,38 @@ +# mypy: allow-untyped-defs """(Disabled by default) support for testing pytest and pytest plugins. PYTEST_DONT_REWRITE """ + +from __future__ import annotations + import collections.abc +from collections.abc import Callable +from collections.abc import Generator +from collections.abc import Iterable +from collections.abc import Sequence 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 fnmatch import fnmatch -from io import StringIO -from pathlib import Path from typing import Any -from typing import Callable -from typing import Dict -from typing import Generator +from typing import Final +from typing import final from typing import IO -from typing import Iterable -from typing import List -from typing import Optional +from typing import Literal from typing import overload -from typing import Sequence from typing import TextIO -from typing import Tuple -from typing import Type from typing import TYPE_CHECKING -from typing import Union from weakref import WeakKeyDictionary from iniconfig import IniConfig @@ -40,7 +41,6 @@ from iniconfig import SectionWrapper from _pytest import timing from _pytest._code import Source from _pytest.capture import _get_multicapture -from _pytest.compat import final from _pytest.compat import NOTSET from _pytest.compat import NotSetType from _pytest.config import _PluggyPlugin @@ -61,18 +61,14 @@ from _pytest.outcomes import fail from _pytest.outcomes import importorskip from _pytest.outcomes import skip from _pytest.pathlib import bestrelpath -from _pytest.pathlib import copytree 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 +from _pytest.warning_types import PytestFDWarning if TYPE_CHECKING: - from typing_extensions import Final - from typing_extensions import Literal - import pexpect @@ -123,14 +119,19 @@ def pytest_configure(config: Config) -> None: class LsofFdLeakChecker: - def get_open_files(self) -> List[Tuple[str, str]]: + 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=locale.getpreferredencoding(False), + encoding=encoding, ).stdout def isopen(line: str) -> bool: @@ -163,36 +164,38 @@ class LsofFdLeakChecker: else: return True - @hookimpl(hookwrapper=True, tryfirst=True) - def pytest_runtest_protocol(self, item: Item) -> Generator[None, None, None]: + @hookimpl(wrapper=True, tryfirst=True) + def pytest_runtest_protocol(self, item: Item) -> Generator[None, object, object]: lines1 = self.get_open_files() - yield - if hasattr(sys, "pypy_version_info"): - gc.collect() - lines2 = 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 = [ - "***** %s FD leakage detected" % len(leaked_files), - *(str(f) for f in leaked_files), - "*** Before:", - *(str(f) for f in lines1), - "*** After:", - *(str(f) for f in lines2), - "***** %s FD leakage detected" % len(leaked_files), - "*** function %s:%s: %s " % item.location, - "See issue #2366", - ] - item.warn(PytestWarning("\n".join(error))) + 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(PytestFDWarning("\n".join(error))) # used at least by pytest-xdist plugin @fixture -def _pytest(request: FixtureRequest) -> "PytestArg": +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.""" @@ -203,13 +206,13 @@ class PytestArg: def __init__(self, request: FixtureRequest) -> None: self._request = request - def gethookrecorder(self, hook) -> "HookRecorder": + 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]: +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] != "_"] @@ -239,8 +242,7 @@ class RecordedHookCall: if TYPE_CHECKING: # The class has undetermined attributes, this tells mypy about it. - def __getattr__(self, key: str): - ... + def __getattr__(self, key: str): ... @final @@ -259,8 +261,8 @@ class HookRecorder: check_ispytest(_ispytest) self._pluginmanager = pluginmanager - self.calls: List[RecordedHookCall] = [] - self.ret: Optional[Union[int, ExitCode]] = None + 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)) @@ -273,17 +275,18 @@ class HookRecorder: def finish_recording(self) -> None: self._undo_wrapping() - def getcalls(self, names: Union[str, Iterable[str]]) -> List[RecordedHookCall]: + 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: + def assert_contains(self, entries: Sequence[tuple[str, str]]) -> None: __tracebackhide__ = True i = 0 entries = list(entries) - backlocals = sys._getframe(1).f_locals + # 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:]): @@ -307,7 +310,7 @@ class HookRecorder: del self.calls[i] return call lines = [f"could not find call {name!r}, in:"] - lines.extend([" %s" % x for x in self.calls]) + lines.extend([f" {x}" for x in self.calls]) fail("\n".join(lines)) def getcall(self, name: str) -> RecordedHookCall: @@ -320,45 +323,42 @@ class HookRecorder: @overload def getreports( self, - names: "Literal['pytest_collectreport']", - ) -> Sequence[CollectReport]: - ... + names: Literal["pytest_collectreport"], + ) -> Sequence[CollectReport]: ... @overload def getreports( self, - names: "Literal['pytest_runtest_logreport']", - ) -> Sequence[TestReport]: - ... + names: Literal["pytest_runtest_logreport"], + ) -> Sequence[TestReport]: ... @overload def getreports( self, - names: Union[str, Iterable[str]] = ( + names: str | Iterable[str] = ( "pytest_collectreport", "pytest_runtest_logreport", ), - ) -> Sequence[Union[CollectReport, TestReport]]: - ... + ) -> Sequence[CollectReport | TestReport]: ... def getreports( self, - names: Union[str, Iterable[str]] = ( + names: str | Iterable[str] = ( "pytest_collectreport", "pytest_runtest_logreport", ), - ) -> Sequence[Union[CollectReport, TestReport]]: + ) -> Sequence[CollectReport | TestReport]: return [x.report for x in self.getcalls(names)] def matchreport( self, inamepart: str = "", - names: Union[str, Iterable[str]] = ( + names: str | Iterable[str] = ( "pytest_runtest_logreport", "pytest_collectreport", ), - when: Optional[str] = None, - ) -> Union[CollectReport, TestReport]: + when: str | None = None, + ) -> CollectReport | TestReport: """Return a testreport whose dotted import path matches.""" values = [] for rep in self.getreports(names=names): @@ -371,48 +371,43 @@ class HookRecorder: values.append(rep) if not values: raise ValueError( - "could not find test report matching %r: " - "no test reports at all!" % (inamepart,) + f"could not find test report matching {inamepart!r}: " + "no test reports at all!" ) if len(values) > 1: raise ValueError( - "found 2 or more testreports matching {!r}: {}".format( - inamepart, values - ) + f"found 2 or more testreports matching {inamepart!r}: {values}" ) return values[0] @overload def getfailures( self, - names: "Literal['pytest_collectreport']", - ) -> Sequence[CollectReport]: - ... + names: Literal["pytest_collectreport"], + ) -> Sequence[CollectReport]: ... @overload def getfailures( self, - names: "Literal['pytest_runtest_logreport']", - ) -> Sequence[TestReport]: - ... + names: Literal["pytest_runtest_logreport"], + ) -> Sequence[TestReport]: ... @overload def getfailures( self, - names: Union[str, Iterable[str]] = ( + names: str | Iterable[str] = ( "pytest_collectreport", "pytest_runtest_logreport", ), - ) -> Sequence[Union[CollectReport, TestReport]]: - ... + ) -> Sequence[CollectReport | TestReport]: ... def getfailures( self, - names: Union[str, Iterable[str]] = ( + names: str | Iterable[str] = ( "pytest_collectreport", "pytest_runtest_logreport", ), - ) -> Sequence[Union[CollectReport, TestReport]]: + ) -> Sequence[CollectReport | TestReport]: return [rep for rep in self.getreports(names) if rep.failed] def getfailedcollections(self) -> Sequence[CollectReport]: @@ -420,10 +415,10 @@ class HookRecorder: def listoutcomes( self, - ) -> Tuple[ + ) -> tuple[ Sequence[TestReport], - Sequence[Union[CollectReport, TestReport]], - Sequence[Union[CollectReport, TestReport]], + Sequence[CollectReport | TestReport], + Sequence[CollectReport | TestReport], ]: passed = [] skipped = [] @@ -442,7 +437,7 @@ class HookRecorder: failed.append(rep) return passed, skipped, failed - def countoutcomes(self) -> List[int]: + 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: @@ -462,14 +457,14 @@ class HookRecorder: @fixture -def linecomp() -> "LineComp": +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"]: +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). @@ -481,7 +476,7 @@ def LineMatcher_fixture(request: FixtureRequest) -> Type["LineMatcher"]: @fixture def pytester( request: FixtureRequest, tmp_path_factory: TempPathFactory, monkeypatch: MonkeyPatch -) -> "Pytester": +) -> Pytester: """ Facilities to write tests/configuration files, execute pytest in isolation, and match against expected output, perfect for black-box testing of pytest plugins. @@ -496,7 +491,7 @@ def pytester( @fixture -def _sys_snapshot() -> Generator[None, None, None]: +def _sys_snapshot() -> Generator[None]: snappaths = SysPathsSnapshot() snapmods = SysModulesSnapshot() yield @@ -505,7 +500,7 @@ def _sys_snapshot() -> Generator[None, None, None]: @fixture -def _config_for_test() -> Generator[Config, None, None]: +def _config_for_test() -> Generator[Config]: from _pytest.config import get_config config = get_config() @@ -525,13 +520,13 @@ class RunResult: def __init__( self, - ret: Union[int, ExitCode], - outlines: List[str], - errlines: List[str], + ret: int | ExitCode, + outlines: list[str], + errlines: list[str], duration: float, ) -> None: try: - self.ret: Union[int, ExitCode] = ExitCode(ret) + self.ret: int | ExitCode = ExitCode(ret) """The return value.""" except ValueError: self.ret = ret @@ -552,11 +547,13 @@ class RunResult: def __repr__(self) -> str: return ( - "" - % (self.ret, len(self.stdout.lines), len(self.stderr.lines), self.duration) + f"" ) - def parseoutcomes(self) -> Dict[str, int]: + def parseoutcomes(self) -> dict[str, int]: """Return a dictionary of outcome noun -> count from parsing the terminal output that the test process produced. @@ -569,7 +566,7 @@ class RunResult: return self.parse_summary_nouns(self.outlines) @classmethod - def parse_summary_nouns(cls, lines) -> Dict[str, int]: + 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:: @@ -600,8 +597,8 @@ class RunResult: errors: int = 0, xpassed: int = 0, xfailed: int = 0, - warnings: Optional[int] = None, - deselected: Optional[int] = None, + warnings: int | None = None, + deselected: int | None = None, ) -> None: """ Assert that the specified outcomes appear with the respective @@ -626,16 +623,8 @@ class RunResult: ) -class CwdSnapshot: - def __init__(self) -> None: - self.__saved = os.getcwd() - - def restore(self) -> None: - os.chdir(self.__saved) - - class SysModulesSnapshot: - def __init__(self, preserve: Optional[Callable[[str], bool]] = None) -> None: + def __init__(self, preserve: Callable[[str], bool] | None = None) -> None: self.__preserve = preserve self.__saved = dict(sys.modules) @@ -668,7 +657,7 @@ class Pytester: __test__ = False - CLOSE_STDIN: "Final" = NOTSET + CLOSE_STDIN: Final = NOTSET class TimeoutExpired(Exception): pass @@ -683,9 +672,9 @@ class Pytester: ) -> None: check_ispytest(_ispytest) self._request = request - self._mod_collections: WeakKeyDictionary[ - Collector, List[Union[Item, Collector]] - ] = WeakKeyDictionary() + self._mod_collections: WeakKeyDictionary[Collector, list[Item | Collector]] = ( + WeakKeyDictionary() + ) if request.function: name: str = request.function.__name__ else: @@ -693,19 +682,20 @@ class Pytester: 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[Union[str, _PluggyPlugin]] = [] - self._cwd_snapshot = CwdSnapshot() + #: :py:meth:`runpytest`. Initially this is an empty list but plugins can + #: be added to the list. + #: + #: When running in subprocess mode, specify plugins by name (str) - adding + #: plugin objects directly is not supported. + self.plugins: list[str | _PluggyPlugin] = [] self._sys_path_snapshot = SysPathsSnapshot() self._sys_modules_snapshot = self.__take_sys_modules_snapshot() - self.chdir() 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) @@ -736,7 +726,6 @@ class Pytester: """ self._sys_modules_snapshot.restore() self._sys_path_snapshot.restore() - self._cwd_snapshot.restore() def __take_sys_modules_snapshot(self) -> SysModulesSnapshot: # Some zope modules used by twisted-related tests keep internal state @@ -761,23 +750,26 @@ class Pytester: This is done automatically upon instantiation. """ - os.chdir(self.path) + self._monkeypatch.chdir(self.path) def _makefile( self, ext: str, - lines: Sequence[Union[Any, bytes]], - files: Dict[str, 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: Union[Any, bytes]) -> str: + def to_text(s: Any | bytes) -> str: return s.decode(encoding) if isinstance(s, bytes) else str(s) if lines: @@ -830,7 +822,7 @@ class Pytester: return self._makefile(ext, args, kwargs) def makeconftest(self, source: str) -> Path: - """Write a contest.py file. + """Write a conftest.py file. :param source: The contents. :returns: The conftest.py file. @@ -845,6 +837,16 @@ class Pytester: """ return self.makefile(".ini", tox=source) + def maketoml(self, source: str) -> Path: + """Write a pytest.toml file. + + :param source: The contents. + :returns: The pytest.toml file. + + .. versionadded:: 9.0 + """ + return self.makefile(".toml", pytest=source) + def getinicfg(self, source: str) -> SectionWrapper: """Return the pytest section from the tox.ini config file.""" p = self.makeini(source) @@ -900,9 +902,7 @@ class Pytester: """ return self._makefile(".txt", args, kwargs) - def syspathinsert( - self, path: Optional[Union[str, "os.PathLike[str]"]] = None - ) -> None: + 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 @@ -916,19 +916,20 @@ class Pytester: self._monkeypatch.syspath_prepend(str(path)) - def mkdir(self, name: Union[str, "os.PathLike[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: Union[str, "os.PathLike[str]"]) -> Path: + 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 @@ -939,13 +940,14 @@ class Pytester: p.joinpath("__init__.py").touch() return p - def copy_example(self, name: Optional[str] = None) -> Path: + 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: @@ -973,7 +975,7 @@ class Pytester: example_path = example_dir.joinpath(name) if example_path.is_dir() and not example_path.joinpath("__init__.py").is_file(): - copytree(example_path, self.path) + 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) @@ -984,9 +986,7 @@ class Pytester: f'example "{example_path}" is not found as a file or directory' ) - def getnode( - self, config: Config, arg: Union[str, "os.PathLike[str]"] - ) -> Union[Collector, Item]: + def getnode(self, config: Config, arg: str | os.PathLike[str]) -> Collector | Item: """Get the collection node of a file. :param config: @@ -1005,9 +1005,7 @@ class Pytester: config.hook.pytest_sessionfinish(session=session, exitstatus=ExitCode.OK) return res - def getpathnode( - self, path: Union[str, "os.PathLike[str]"] - ) -> Union[Collector, Item]: + 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 @@ -1027,7 +1025,7 @@ class Pytester: config.hook.pytest_sessionfinish(session=session, exitstatus=ExitCode.OK) return res - def genitems(self, colitems: Sequence[Union[Item, Collector]]) -> List[Item]: + 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 @@ -1039,7 +1037,7 @@ class Pytester: The collected items. """ session = colitems[0].session - result: List[Item] = [] + result: list[Item] = [] for colitem in colitems: result.extend(session.genitems(colitem)) return result @@ -1050,7 +1048,7 @@ class Pytester: 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. - :py:func:`_pytest.runner.runtestprotocol`. + ``_pytest.runner.runtestprotocol``. """ # used from runner functional tests item = self.getitem(source) @@ -1070,11 +1068,11 @@ class Pytester: :param cmdlineargs: Any extra command line arguments to use. """ p = self.makepyfile(source) - values = list(cmdlineargs) + [p] + values = [*list(cmdlineargs), p] return self.inline_run(*values) - def inline_genitems(self, *args) -> Tuple[List[Item], HookRecorder]: - """Run ``pytest.main(['--collectonly'])`` in-process. + 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 @@ -1086,7 +1084,7 @@ class Pytester: def inline_run( self, - *args: Union[str, "os.PathLike[str]"], + *args: str | os.PathLike[str], plugins=(), no_reraise_ctrlc: bool = False, ) -> HookRecorder: @@ -1106,6 +1104,8 @@ class Pytester: Typically we reraise keyboard interrupts from the child run. If True, the KeyboardInterrupt exception is captured. """ + from _pytest.unraisableexception import gc_collect_iterations_key + # (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) @@ -1129,11 +1129,16 @@ class Pytester: rec = [] - class Collect: - def pytest_configure(x, config: Config) -> None: + class PytesterHelperPlugin: + @staticmethod + def pytest_configure(config: Config) -> None: rec.append(self.make_hook_recorder(config.pluginmanager)) - plugins.append(Collect()) + # The unraisable plugin GC collect slows down inline + # pytester runs too much. + config.stash[gc_collect_iterations_key] = 0 + + plugins.append(PytesterHelperPlugin()) ret = main([str(x) for x in args], plugins=plugins) if len(rec) == 1: reprec = rec.pop() @@ -1156,7 +1161,7 @@ class Pytester: finalizer() def runpytest_inprocess( - self, *args: Union[str, "os.PathLike[str]"], **kwargs: Any + 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.""" @@ -1164,7 +1169,7 @@ class Pytester: if syspathinsert: self.syspathinsert() - now = timing.time() + instant = timing.Instant() capture = _get_multicapture("sys") capture.start_capturing() try: @@ -1194,14 +1199,12 @@ class Pytester: assert reprec.ret is not None res = RunResult( - reprec.ret, out.splitlines(), err.splitlines(), timing.time() - now + reprec.ret, out.splitlines(), err.splitlines(), instant.elapsed().seconds ) res.reprec = reprec # type: ignore return res - def runpytest( - self, *args: Union[str, "os.PathLike[str]"], **kwargs: Any - ) -> RunResult: + 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) @@ -1212,17 +1215,19 @@ class Pytester: raise RuntimeError(f"Unrecognized runpytest option: {self._method}") def _ensure_basetemp( - self, args: Sequence[Union[str, "os.PathLike[str]"]] - ) -> List[Union[str, "os.PathLike[str]"]]: + 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=%s" % self.path.parent.joinpath("basetemp")) + new_args.append( + "--basetemp={}".format(self.path.parent.joinpath("basetemp")) + ) return new_args - def parseconfig(self, *args: Union[str, "os.PathLike[str]"]) -> Config: + def parseconfig(self, *args: str | os.PathLike[str]) -> Config: """Return a new pytest :class:`pytest.Config` instance from given commandline args. @@ -1236,17 +1241,16 @@ class Pytester: """ import _pytest.config - new_args = self._ensure_basetemp(args) - new_args = [str(x) for x in new_args] + new_args = [str(x) for x in self._ensure_basetemp(args)] - config = _pytest.config._prepareconfig(new_args, self.plugins) # type: ignore[arg-type] + config = _pytest.config._prepareconfig(new_args, self.plugins) # 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: Union[str, "os.PathLike[str]"]) -> 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 @@ -1258,7 +1262,7 @@ class Pytester: return config def getitem( - self, source: Union[str, "os.PathLike[str]"], funcname: str = "test_func" + self, source: str | os.PathLike[str], funcname: str = "test_func" ) -> Item: """Return the test item for a test function. @@ -1277,11 +1281,9 @@ class Pytester: for item in items: if item.name == funcname: return item - assert 0, "{!r} item not found in module:\n{}\nitems: {}".format( - funcname, source, items - ) + assert 0, f"{funcname!r} item not found in module:\n{source}\nitems: {items}" - def getitems(self, source: Union[str, "os.PathLike[str]"]) -> List[Item]: + 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 @@ -1292,7 +1294,7 @@ class Pytester: def getmodulecol( self, - source: Union[str, "os.PathLike[str]"], + source: str | os.PathLike[str], configargs=(), *, withinit: bool = False, @@ -1324,9 +1326,7 @@ class Pytester: self.config = config = self.parseconfigure(path, *configargs) return self.getnode(config, path) - def collect_by_name( - self, modcol: Collector, name: str - ) -> Optional[Union[Item, Collector]]: + 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 @@ -1344,10 +1344,10 @@ class Pytester: def popen( self, - cmdargs: Sequence[Union[str, "os.PathLike[str]"]], - stdout: Union[int, TextIO] = subprocess.PIPE, - stderr: Union[int, TextIO] = subprocess.PIPE, - stdin: Union[NotSetType, bytes, IO[Any], int] = CLOSE_STDIN, + 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`. @@ -1382,9 +1382,9 @@ class Pytester: def run( self, - *cmdargs: Union[str, "os.PathLike[str]"], - timeout: Optional[float] = None, - stdin: Union[NotSetType, bytes, IO[Any], int] = CLOSE_STDIN, + *cmdargs: str | os.PathLike[str], + timeout: float | None = None, + stdin: NotSetType | bytes | IO[Any] | int = CLOSE_STDIN, ) -> RunResult: """Run a command with arguments. @@ -1401,7 +1401,7 @@ class Pytester: :param stdin: Optional standard input. - - If it is :py:attr:`CLOSE_STDIN` (Default), then this method calls + - 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. @@ -1412,8 +1412,10 @@ class Pytester: - 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 @@ -1424,13 +1426,12 @@ class Pytester: print(" in:", Path.cwd()) with p1.open("w", encoding="utf8") as f1, p2.open("w", encoding="utf8") as f2: - now = timing.time() + instant = timing.Instant() popen = self.popen( cmdargs, stdin=stdin, stdout=f1, stderr=f2, - close_fds=(sys.platform != "win32"), ) if popen.stdin is not None: popen.stdin.close() @@ -1438,10 +1439,7 @@ class Pytester: def handle_timeout() -> None: __tracebackhide__ = True - timeout_message = ( - "{seconds} second timeout expired running:" - " {command}".format(seconds=timeout, command=cmdargs) - ) + timeout_message = f"{timeout} second timeout expired running: {cmdargs}" popen.kill() popen.wait() @@ -1454,6 +1452,8 @@ class Pytester: ret = popen.wait(timeout) except subprocess.TimeoutExpired: handle_timeout() + f1.flush() + f2.flush() with p1.open(encoding="utf8") as f1, p2.open(encoding="utf8") as f2: out = f1.read().splitlines() @@ -1464,7 +1464,7 @@ class Pytester: with contextlib.suppress(ValueError): ret = ExitCode(ret) - return RunResult(ret, out, err, timing.time() - now) + return RunResult(ret, out, err, instant.elapsed().seconds) def _dump_lines(self, lines, fp): try: @@ -1473,10 +1473,10 @@ class Pytester: except UnicodeEncodeError: print(f"couldn't print to {fp} because of encoding") - def _getpytestargs(self) -> Tuple[str, ...]: + def _getpytestargs(self) -> tuple[str, ...]: return sys.executable, "-mpytest" - def runpython(self, script: "os.PathLike[str]") -> RunResult: + def runpython(self, script: os.PathLike[str]) -> RunResult: """Run a python script using sys.executable as interpreter.""" return self.run(sys.executable, script) @@ -1485,7 +1485,7 @@ class Pytester: return self.run(sys.executable, "-c", command) def runpytest_subprocess( - self, *args: Union[str, "os.PathLike[str]"], timeout: Optional[float] = None + self, *args: str | os.PathLike[str], timeout: float | None = None ) -> RunResult: """Run pytest as a subprocess with given arguments. @@ -1505,16 +1505,18 @@ class Pytester: """ __tracebackhide__ = True p = make_numbered_dir(root=self.path, prefix="runpytest-", mode=0o700) - args = ("--basetemp=%s" % p,) + args - plugins = [x for x in self.plugins if isinstance(x, str)] - if plugins: - args = ("-p", plugins[0]) + args + args = (f"--basetemp={p}", *args) + for plugin in self.plugins: + if not isinstance(plugin, str): + raise ValueError( + f"Specifying plugins as objects is not supported in pytester subprocess mode; " + f"specify by name instead: {plugin}" + ) + args = ("-p", plugin, *args) args = self._getpytestargs() + args return self.run(*args, timeout=timeout) - def spawn_pytest( - self, string: str, expect_timeout: float = 10.0 - ) -> "pexpect.spawn": + 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 @@ -1528,7 +1530,7 @@ class Pytester: 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": + def spawn(self, cmd: str, expect_timeout: float = 10.0) -> pexpect.spawn: """Run a command using pexpect. The pexpect child is returned. @@ -1573,9 +1575,9 @@ class LineMatcher: ``text.splitlines()``. """ - def __init__(self, lines: List[str]) -> None: + def __init__(self, lines: list[str]) -> None: self.lines = lines - self._log_output: List[str] = [] + self._log_output: list[str] = [] def __str__(self) -> str: """Return the entire original text. @@ -1585,7 +1587,7 @@ class LineMatcher: """ return "\n".join(self.lines) - def _getlines(self, lines2: Union[str, Sequence[str], Source]) -> Sequence[str]: + def _getlines(self, lines2: str | Sequence[str] | Source) -> Sequence[str]: if isinstance(lines2, str): lines2 = Source(lines2) if isinstance(lines2, Source): @@ -1613,7 +1615,7 @@ class LineMatcher: self._log("matched: ", repr(line)) break else: - msg = "line %r not found in output" % line + msg = f"line {line!r} not found in output" self._log(msg) self._fail(msg) @@ -1625,7 +1627,7 @@ class LineMatcher: for i, line in enumerate(self.lines): if fnline == line or fnmatch(line, fnline): return self.lines[i + 1 :] - raise ValueError("line %r not found in output" % fnline) + 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)) @@ -1710,7 +1712,7 @@ class LineMatcher: started = True break elif match_func(nextline, line): - self._log("%s:" % match_nickname, repr(line)) + self._log(f"{match_nickname}:", repr(line)) self._log( "{:>{width}}".format("with:", width=wnick), repr(nextline) ) diff --git a/venv/lib/python3.10/site-packages/_pytest/pytester_assertions.py b/venv/lib/python3.10/site-packages/_pytest/pytester_assertions.py index 657e4db..915cc8a 100644 --- a/venv/lib/python3.10/site-packages/_pytest/pytester_assertions.py +++ b/venv/lib/python3.10/site-packages/_pytest/pytester_assertions.py @@ -1,23 +1,22 @@ """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 typing import Dict -from typing import Optional -from typing import Sequence -from typing import Tuple -from typing import Union +from __future__ import annotations + +from collections.abc import Sequence from _pytest.reports import CollectReport from _pytest.reports import TestReport def assertoutcome( - outcomes: Tuple[ + outcomes: tuple[ Sequence[TestReport], - Sequence[Union[CollectReport, TestReport]], - Sequence[Union[CollectReport, TestReport]], + Sequence[CollectReport | TestReport], + Sequence[CollectReport | TestReport], ], passed: int = 0, skipped: int = 0, @@ -36,15 +35,15 @@ def assertoutcome( def assert_outcomes( - outcomes: Dict[str, int], + outcomes: dict[str, int], passed: int = 0, skipped: int = 0, failed: int = 0, errors: int = 0, xpassed: int = 0, xfailed: int = 0, - warnings: Optional[int] = None, - deselected: Optional[int] = None, + 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.""" diff --git a/venv/lib/python3.10/site-packages/_pytest/python.py b/venv/lib/python3.10/site-packages/_pytest/python.py index 5f8be5d..e637518 100644 --- a/venv/lib/python3.10/site-packages/_pytest/python.py +++ b/venv/lib/python3.10/site-packages/_pytest/python.py @@ -1,32 +1,35 @@ +# 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 +from collections.abc import Callable +from collections.abc import Generator +from collections.abc import Iterable +from collections.abc import Iterator +from collections.abc import Mapping +from collections.abc import Sequence import dataclasses import enum import fnmatch +from functools import partial import inspect import itertools import os -import sys -import types -import warnings -from collections import Counter -from collections import defaultdict -from functools import partial from pathlib import Path +import re +import textwrap +import types from typing import Any -from typing import Callable -from typing import Dict -from typing import Generator -from typing import Iterable -from typing import Iterator -from typing import List -from typing import Mapping -from typing import Optional -from typing import Pattern -from typing import Sequence -from typing import Set -from typing import Tuple +from typing import cast +from typing import final +from typing import Literal +from typing import NoReturn from typing import TYPE_CHECKING -from typing import Union +import warnings import _pytest from _pytest import fixtures @@ -36,77 +39,50 @@ 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 import TerminalWriter from _pytest._io.saferepr import saferepr from _pytest.compat import ascii_escaped -from _pytest.compat import assert_never -from _pytest.compat import final from _pytest.compat import get_default_arg_names from _pytest.compat import get_real_func from _pytest.compat import getimfunc -from _pytest.compat import getlocation 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.compat import STRING_TYPES 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.deprecated import INSTANCE_COLLECTOR -from _pytest.deprecated import NOSE_SUPPORT_METHOD +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 _HiddenParam from _pytest.mark.structures import get_unpacked_marks +from _pytest.mark.structures import HIDDEN_PARAM 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 bestrelpath from _pytest.pathlib import fnmatch_ex from _pytest.pathlib import import_path from _pytest.pathlib import ImportPathMismatchError -from _pytest.pathlib import parts -from _pytest.pathlib import visit +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 Literal - - from _pytest.scope import _ScopeName - - -_PYTEST_DIR = Path(_pytest.__file__).parent + from typing_extensions import Self def pytest_addoption(parser: Parser) -> None: - 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", - ) parser.addini( "python_files", type="args", @@ -133,19 +109,16 @@ def pytest_addoption(parser: Parser) -> None: help="Disable string escape non-ASCII characters, might cause unwanted " "side effects(use at your own risk)", ) + parser.addini( + "strict_parametrization_ids", + type="bool", + # None => fallback to `strict`. + default=None, + help="Emit an error if non-unique parameter set IDs are detected", + ) -def pytest_cmdline_main(config: Config) -> Optional[Union[int, ExitCode]]: - 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 pytest_generate_tests(metafunc: "Metafunc") -> None: +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) @@ -170,45 +143,59 @@ def pytest_configure(config: Config) -> None: ) -def async_warn_and_skip(nodeid: str) -> None: - msg = "async def functions are not natively supported and have been skipped.\n" - msg += ( +def async_fail(nodeid: str) -> None: + msg = ( + "async def functions are not natively supported.\n" "You need to install a suitable plugin for your async framework, for example:\n" + " - anyio\n" + " - pytest-asyncio\n" + " - pytest-tornasync\n" + " - pytest-trio\n" + " - pytest-twisted" ) - 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)") + fail(msg, pytrace=False) @hookimpl(trylast=True) -def pytest_pyfunc_call(pyfuncitem: "Function") -> Optional[object]: +def pytest_pyfunc_call(pyfuncitem: Function) -> object | None: testfunction = pyfuncitem.obj if is_async_function(testfunction): - async_warn_and_skip(pyfuncitem.nodeid) + async_fail(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) + async_fail(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`?" + f"Test functions should return None, but {pyfuncitem.nodeid} returned {type(result)!r}.\n" + "Did you mean to use `assert` instead of `return`?\n" + "See https://docs.pytest.org/en/stable/how-to/assert.html#return-not-none for more information." ) ) return True -def pytest_collect_file(file_path: Path, parent: nodes.Collector) -> Optional["Module"]: +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") + ["__init__.py"] + file_path, parent.config.getini("python_files") ): return None ihook = parent.session.gethookproxy(file_path) @@ -224,24 +211,19 @@ def path_matches_patterns(path: Path, patterns: Iterable[str]) -> bool: return any(fnmatch_ex(pattern, path) for pattern in patterns) -def pytest_pycollect_makemodule(module_path: Path, parent) -> "Module": - if module_path.name == "__init__.py": - pkg: Package = Package.from_parent(parent, path=module_path) - return pkg - mod: Module = Module.from_parent(parent, path=module_path) - return mod +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: Union["Module", "Class"], name: str, obj: object -) -> Union[None, nodes.Item, nodes.Collector, List[Union[nodes.Item, nodes.Collector]]]: - assert isinstance(collector, (Class, Module)), type(collector) + 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): - klass: Class = Class.from_parent(collector, name=name, obj=obj) - return klass + 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) @@ -252,23 +234,20 @@ def pytest_pycollect_makeitem( filename, lineno = getfslineno(obj) warnings.warn_explicit( message=PytestCollectionWarning( - "cannot collect %r because it is not a function." % name + 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 = Function.from_parent(collector, name=name) - reason = "yield tests were removed in pytest 4.0 - {name} will be ignored".format( - name=name + if inspect.isgeneratorfunction(obj): + fail( + f"'yield' keyword is allowed in fixtures, but not in tests ({name})", + pytrace=False, ) - res.add_marker(MARK_GEN.xfail(run=False, reason=reason)) - res.warn(PytestCollectionWarning(reason)) - return res - else: - return list(collector._genfunctions(name, obj)) + return list(collector._genfunctions(name, obj)) + return None return None @@ -297,10 +276,10 @@ class PyobjMixin(nodes.Node): """Python instance object the function is bound to. Returns None if not a test method, e.g. for a standalone test function, - a staticmethod, a class or a module. + a class or a module. """ - node = self.getparent(Function) - return getattr(node.obj, "__self__", None) if node is not None else None + # Overridden by Function. + return None @property def obj(self): @@ -330,10 +309,8 @@ class PyobjMixin(nodes.Node): def getmodpath(self, stopatmodule: bool = True, includemodule: bool = False) -> str: """Return Python path relative to the containing module.""" - chain = self.listchain() - chain.reverse() parts = [] - for node in chain: + for node in self.iter_parents(): name = node.name if isinstance(node, Module): name = os.path.splitext(name)[0] @@ -345,22 +322,10 @@ class PyobjMixin(nodes.Node): parts.reverse() return ".".join(parts) - def reportinfo(self) -> Tuple[Union["os.PathLike[str]", str], Optional[int], str]: + def reportinfo(self) -> tuple[os.PathLike[str] | str, int | None, str]: # XXX caching? - obj = self.obj - compat_co_firstlineno = getattr(obj, "compat_co_firstlineno", None) - if isinstance(compat_co_firstlineno, int): - # nose compatibility - file_path = sys.modules[obj.__module__].__file__ - assert file_path is not None - if file_path.endswith(".pyc"): - file_path = file_path[:-1] - path: Union["os.PathLike[str]", str] = file_path - lineno = compat_co_firstlineno - else: - path, lineno = getfslineno(obj) + path, lineno = getfslineno(self.obj) modpath = self.getmodpath() - assert isinstance(lineno, int) return path, lineno, modpath @@ -369,7 +334,7 @@ class PyobjMixin(nodes.Node): # hook is not called for them. # fmt: off class _EmptyClass: pass # noqa: E701 -IGNORED_ATTRIBUTES = frozenset.union( # noqa: E305 +IGNORED_ATTRIBUTES = frozenset.union( frozenset(), # Module. dir(types.ModuleType("empty_module")), @@ -384,7 +349,7 @@ del _EmptyClass # fmt: on -class PyCollector(PyobjMixin, nodes.Collector): +class PyCollector(PyobjMixin, nodes.Collector, abc.ABC): def funcnamefilter(self, name: str) -> bool: return self._matches_prefix_or_glob_option("python_functions", name) @@ -402,7 +367,7 @@ class PyCollector(PyobjMixin, nodes.Collector): def istestfunction(self, obj: object, name: str) -> bool: if self.funcnamefilter(name) or self.isnosetest(obj): - if isinstance(obj, (staticmethod, classmethod)): + 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 @@ -410,11 +375,15 @@ class PyCollector(PyobjMixin, nodes.Collector): return False def istestclass(self, obj: object, name: str) -> bool: - return self.classnamefilter(name) or self.isnosetest(obj) + 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.""" + in configuration.""" for option in self.config.getini(option_name): if name.startswith(option): return True @@ -427,7 +396,7 @@ class PyCollector(PyobjMixin, nodes.Collector): return True return False - def collect(self) -> Iterable[Union[nodes.Item, nodes.Collector]]: + def collect(self) -> Iterable[nodes.Item | nodes.Collector]: if not getattr(self.obj, "__test__", True): return [] @@ -439,11 +408,12 @@ class PyCollector(PyobjMixin, nodes.Collector): # In each class, nodes should be definition ordered. # __dict__ is definition ordered. - seen: Set[str] = set() - dict_values: List[List[Union[nodes.Item, nodes.Collector]]] = [] + seen: set[str] = set() + dict_values: list[list[nodes.Item | nodes.Collector]] = [] + collect_imported_tests = self.session.config.getini("collect_imported_tests") ihook = self.ihook for dic in dicts: - values: List[Union[nodes.Item, nodes.Collector]] = [] + 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()): @@ -452,6 +422,13 @@ class PyCollector(PyobjMixin, nodes.Collector): if name in seen: continue seen.add(name) + + if not collect_imported_tests and isinstance(self, Module): + # Do not collect functions and classes from other modules. + if inspect.isfunction(obj) or inspect.isclass(obj): + if obj.__module__ != self._getobj().__name__: + continue + res = ihook.pytest_pycollect_makeitem( collector=self, name=name, obj=obj ) @@ -470,12 +447,12 @@ class PyCollector(PyobjMixin, nodes.Collector): result.extend(values) return result - def _genfunctions(self, name: str, funcobj) -> Iterator["Function"]: + 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 + cls = (clscol and clscol.obj) or None definition = FunctionDefinition.from_parent(self, name=name, callobj=funcobj) fixtureinfo = definition._fixtureinfo @@ -500,17 +477,16 @@ class PyCollector(PyobjMixin, nodes.Collector): if not metafunc._calls: yield Function.from_parent(self, name=name, fixtureinfo=fixtureinfo) else: - # Add funcargs() as fixturedefs to fixtureinfo.arg2fixturedefs. - fm = self.session._fixturemanager - fixtures.add_funcarg_pseudo_fixture_def(self, metafunc, fm) - - # Add_funcarg_pseudo_fixture_def may have shadowed some fixtures - # with direct parametrization, so make sure we update what the - # function really needs. + metafunc._recompute_direct_params_indices() + # 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}]" + subname = f"{name}[{callspec.id}]" if callspec._idlist else name yield Function.from_parent( self, name=subname, @@ -521,63 +497,110 @@ class PyCollector(PyobjMixin, nodes.Collector): ) +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 self._importtestmodule() + return importtestmodule(self.path, self.config) - def collect(self) -> Iterable[Union[nodes.Item, nodes.Collector]]: - self._inject_setup_module_fixture() - self._inject_setup_function_fixture() + 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 _inject_setup_module_fixture(self) -> None: - """Inject a hidden autouse, module scoped fixture into the collected module object + 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). """ - has_nose = self.config.pluginmanager.has_plugin("nose") setup_module = _get_first_non_fixture_func( self.obj, ("setUpModule", "setup_module") ) - if setup_module is None and has_nose: - # The name "setup" is too common - only treat as fixture if callable. - setup_module = _get_first_non_fixture_func(self.obj, ("setup",)) - if not callable(setup_module): - setup_module = None teardown_module = _get_first_non_fixture_func( self.obj, ("tearDownModule", "teardown_module") ) - if teardown_module is None and has_nose: - teardown_module = _get_first_non_fixture_func(self.obj, ("teardown",)) - # Same as "setup" above - only treat as fixture if callable. - if not callable(teardown_module): - teardown_module = None if setup_module is None and teardown_module is None: return - @fixtures.fixture( - autouse=True, - scope="module", - # Use a unique name to speed up lookup. - name=f"_xunit_setup_module_fixture_{self.obj.__name__}", - ) - def xunit_setup_module_fixture(request) -> Generator[None, None, None]: + def xunit_setup_module_fixture(request) -> Generator[None]: + module = request.module if setup_module is not None: - _call_with_optional_argument(setup_module, request.module) + _call_with_optional_argument(setup_module, module) yield if teardown_module is not None: - _call_with_optional_argument(teardown_module, request.module) + _call_with_optional_argument(teardown_module, module) - self.obj.__pytest_setup_module = xunit_setup_module_fixture + 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 _inject_setup_function_fixture(self) -> None: - """Inject a hidden autouse, function scoped fixture into the collected module object + 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 @@ -590,93 +613,58 @@ class Module(nodes.File, PyCollector): if setup_function is None and teardown_function is None: return - @fixtures.fixture( - autouse=True, - scope="function", - # Use a unique name to speed up lookup. - name=f"_xunit_setup_function_fixture_{self.obj.__name__}", - ) - def xunit_setup_function_fixture(request) -> Generator[None, None, None]: + 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, request.function) + _call_with_optional_argument(setup_function, function) yield if teardown_function is not None: - _call_with_optional_argument(teardown_function, request.function) + _call_with_optional_argument(teardown_function, function) - self.obj.__pytest_setup_function = xunit_setup_function_fixture - - def _importtestmodule(self): - # We assume we are only called once per module. - importmode = self.config.getoption("--import-mode") - try: - mod = import_path(self.path, mode=importmode, root=self.config.rootpath) - except SyntaxError as e: - raise self.CollectError( - ExceptionInfo.from_current().getrepr(style="short") - ) from e - except ImportPathMismatchError as e: - raise self.CollectError( - "import file mismatch:\n" - "imported module %r has this __file__ attribute:\n" - " %s\n" - "which is not the same as the test file we want to collect:\n" - " %s\n" - "HINT: remove __pycache__ / .pyc files and/or use a " - "unique basename for your test file modules" % e.args - ) from e - except ImportError as e: - exc_info = ExceptionInfo.from_current() - if self.config.getoption("verbose") < 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 self.CollectError( - "ImportError while importing test module '{path}'.\n" - "Hint: make sure your test modules/packages have valid Python names.\n" - "Traceback:\n" - "{traceback}".format(path=self.path, traceback=formatted_tb) - ) from e - except skip.Exception as e: - if e.allow_module_level: - raise - raise self.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 - self.config.pluginmanager.consider_module(mod) - return mod + 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(Module): +class Package(nodes.Directory): """Collector for files and directories in a Python packages -- directories - with an `__init__.py` file.""" + 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: Optional[LEGACY_PATH], + fspath: LEGACY_PATH | None, parent: nodes.Collector, # NOTE: following args are unused: config=None, session=None, nodeid=None, - path: Optional[Path] = None, + path: Path | None = None, ) -> None: # NOTE: Could be just the following, but kept as-is for compat. - # nodes.FSCollector.__init__(self, fspath, parent=parent) + # super().__init__(self, fspath, parent=parent) session = parent.session - nodes.FSCollector.__init__( - self, + super().__init__( fspath=fspath, path=path, parent=parent, @@ -684,87 +672,51 @@ class Package(Module): session=session, nodeid=nodeid, ) - self.name = self.path.parent.name 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( - self.obj, ("setUpModule", "setup_module") + init_mod, ("setUpModule", "setup_module") ) if setup_module is not None: - _call_with_optional_argument(setup_module, self.obj) + _call_with_optional_argument(setup_module, init_mod) teardown_module = _get_first_non_fixture_func( - self.obj, ("tearDownModule", "teardown_module") + init_mod, ("tearDownModule", "teardown_module") ) if teardown_module is not None: - func = partial(_call_with_optional_argument, teardown_module, self.obj) + func = partial(_call_with_optional_argument, teardown_module, init_mod) self.addfinalizer(func) - def _recurse(self, direntry: "os.DirEntry[str]") -> bool: - if direntry.name == "__pycache__": - return False - fspath = Path(direntry.path) - ihook = self.session.gethookproxy(fspath.parent) - if ihook.pytest_ignore_collect(collection_path=fspath, config=self.config): - return False - return True + 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) - def _collectfile( - self, fspath: Path, handle_dupes: bool = True - ) -> Sequence[nodes.Collector]: - assert ( - fspath.is_file() - ), "{!r} is not a file (isdir={!r}, exists={!r}, islink={!r})".format( - fspath, fspath.is_dir(), fspath.exists(), fspath.is_symlink() - ) - ihook = self.session.gethookproxy(fspath) - if not self.session.isinitpath(fspath): - if ihook.pytest_ignore_collect(collection_path=fspath, config=self.config): - return () + 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 - if handle_dupes: - keepduplicates = self.config.getoption("keepduplicates") - if not keepduplicates: - duplicate_paths = self.config.pluginmanager._duplicatepaths - if fspath in duplicate_paths: - return () - else: - duplicate_paths.add(fspath) - - return ihook.pytest_collect_file(file_path=fspath, parent=self) # type: ignore[no-any-return] - - def collect(self) -> Iterable[Union[nodes.Item, nodes.Collector]]: - this_path = self.path.parent - - # Always collect the __init__ first. - if path_matches_patterns(self.path, self.config.getini("python_files")): - yield Module.from_parent(self, path=self.path) - - pkg_prefixes: Set[Path] = set() - for direntry in visit(str(this_path), recurse=self._recurse): - path = Path(direntry.path) - - # We will visit our own __init__.py file, in which case we skip it. - if direntry.is_file(): - if direntry.name == "__init__.py" and path.parent == this_path: - continue - - parts_ = parts(direntry.path) - if any( - str(pkg_prefix) in parts_ and pkg_prefix / "__init__.py" != path - for pkg_prefix in pkg_prefixes - ): - continue - - if direntry.is_file(): - yield from self._collectfile(path) - elif not direntry.is_dir(): - # Broken symlink or invalid/missing file. - continue - elif path.joinpath("__init__.py").is_file(): - pkg_prefixes.add(path) + 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: @@ -779,12 +731,12 @@ def _call_with_optional_argument(func, arg) -> None: func() -def _get_first_non_fixture_func(obj: object, names: Iterable[str]) -> Optional[object]: +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: Optional[object] = getattr(obj, name, None) + meth: object | None = getattr(obj, name, None) if meth is not None and fixtures.getfixturemarker(meth) is None: return meth return None @@ -794,23 +746,22 @@ class Class(PyCollector): """Collector for test methods (and nested classes) in a Python class.""" @classmethod - def from_parent(cls, parent, *, name, obj=None, **kw): + 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[Union[nodes.Item, nodes.Collector]]: + 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( - "cannot collect test class %r because it has a " - "__init__ constructor (from: %s)" - % (self.obj.__name__, self.parent.nodeid) + f"cannot collect test class {self.obj.__name__!r} because it has a " + f"__init__ constructor (from: {self.parent.nodeid})" ) ) return [] @@ -818,22 +769,21 @@ class Class(PyCollector): assert self.parent is not None self.warn( PytestCollectionWarning( - "cannot collect test class %r because it has a " - "__new__ constructor (from: %s)" - % (self.obj.__name__, self.parent.nodeid) + f"cannot collect test class {self.obj.__name__!r} because it has a " + f"__new__ constructor (from: {self.parent.nodeid})" ) ) return [] - self._inject_setup_class_fixture() - self._inject_setup_method_fixture() + self._register_setup_class_fixture() + self._register_setup_method_fixture() self.session._fixturemanager.parsefactories(self.newinstance(), self.nodeid) return super().collect() - def _inject_setup_class_fixture(self) -> None: - """Inject a hidden autouse, class scoped fixture into the collected class object + 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 @@ -844,93 +794,58 @@ class Class(PyCollector): if setup_class is None and teardown_class is None: return - @fixtures.fixture( - autouse=True, - scope="class", - # Use a unique name to speed up lookup. - name=f"_xunit_setup_class_fixture_{self.obj.__qualname__}", - ) - def xunit_setup_class_fixture(cls) -> Generator[None, None, None]: + 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, self.obj) + _call_with_optional_argument(func, cls) yield if teardown_class is not None: func = getimfunc(teardown_class) - _call_with_optional_argument(func, self.obj) + _call_with_optional_argument(func, cls) - self.obj.__pytest_setup_class = xunit_setup_class_fixture + 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 _inject_setup_method_fixture(self) -> None: - """Inject a hidden autouse, function scoped fixture into the collected class object + 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). """ - has_nose = self.config.pluginmanager.has_plugin("nose") setup_name = "setup_method" setup_method = _get_first_non_fixture_func(self.obj, (setup_name,)) - emit_nose_setup_warning = False - if setup_method is None and has_nose: - setup_name = "setup" - emit_nose_setup_warning = True - 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,)) - emit_nose_teardown_warning = False - if teardown_method is None and has_nose: - teardown_name = "teardown" - emit_nose_teardown_warning = True - teardown_method = _get_first_non_fixture_func(self.obj, (teardown_name,)) if setup_method is None and teardown_method is None: return - @fixtures.fixture( - autouse=True, - scope="function", - # Use a unique name to speed up lookup. - name=f"_xunit_setup_method_fixture_{self.obj.__qualname__}", - ) - def xunit_setup_method_fixture(self, request) -> Generator[None, None, None]: + def xunit_setup_method_fixture(request) -> Generator[None]: + instance = request.instance method = request.function if setup_method is not None: - func = getattr(self, setup_name) + func = getattr(instance, setup_name) _call_with_optional_argument(func, method) - if emit_nose_setup_warning: - warnings.warn( - NOSE_SUPPORT_METHOD.format( - nodeid=request.node.nodeid, method="setup" - ), - stacklevel=2, - ) yield if teardown_method is not None: - func = getattr(self, teardown_name) + func = getattr(instance, teardown_name) _call_with_optional_argument(func, method) - if emit_nose_teardown_warning: - warnings.warn( - NOSE_SUPPORT_METHOD.format( - nodeid=request.node.nodeid, method="teardown" - ), - stacklevel=2, - ) - self.obj.__pytest_setup_method = xunit_setup_method_fixture - - -class InstanceDummy: - """Instance used to be a node type between Class and Function. It has been - removed in pytest 7.0. Some plugins exist which reference `pytest.Instance` - only to ignore it; this dummy class keeps them working. This will be removed - in pytest 8.""" - - -def __getattr__(name: str) -> object: - if name == "Instance": - warnings.warn(INSTANCE_COLLECTOR, 2) - return InstanceDummy - raise AttributeError(f"module {__name__} has no attribute {name}") + 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: @@ -954,12 +869,12 @@ class IdMaker: __slots__ = ( "argnames", - "parametersets", + "config", + "func_name", "idfn", "ids", - "config", "nodeid", - "func_name", + "parametersets", ) # The argnames of the parametrization. @@ -968,24 +883,27 @@ class IdMaker: parametersets: Sequence[ParameterSet] # Optionally, a user-provided callable to make IDs for parameters in a # ParameterSet. - idfn: Optional[Callable[[Any], Optional[object]]] + idfn: Callable[[Any], object | None] | None # Optionally, explicit IDs for ParameterSets by index. - ids: Optional[Sequence[Optional[object]]] + 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: Optional[Config] + # Used for controlling ASCII escaping, determining parametrization ID + # strictness, 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: Optional[str] + nodeid: str | None # Optionally, the ID of the function being parametrized. # Used only for clearer error messages. - func_name: Optional[str] + func_name: str | None - def make_unique_parameterset_ids(self) -> List[str]: + def make_unique_parameterset_ids(self) -> list[str | _HiddenParam]: """Make a unique identifier for each ParameterSet, that may be used to identify the parametrization in a node ID. + If strict_parametrization_ids is enabled, and duplicates are detected, + raises CollectError. Otherwise makes the IDs unique as follows: + Format is -...-[counter], where prm_x_token is - user-provided id, if given - else an id derived from the value, applicable for certain types @@ -998,29 +916,84 @@ class IdMaker: if len(resolved_ids) != len(set(resolved_ids)): # Record the number of occurrences of each ID. id_counts = Counter(resolved_ids) + + if self._strict_parametrization_ids_enabled(): + parameters = ", ".join(self.argnames) + parametersets = ", ".join( + [saferepr(list(param.values)) for param in self.parametersets] + ) + ids = ", ".join( + id if id is not HIDDEN_PARAM else "" for id in resolved_ids + ) + duplicates = ", ".join( + id if id is not HIDDEN_PARAM else "" + for id, count in id_counts.items() + if count > 1 + ) + msg = textwrap.dedent(f""" + Duplicate parametrization IDs detected, but strict_parametrization_ids is set. + + Test name: {self.nodeid} + Parameters: {parameters} + Parameter sets: {parametersets} + IDs: {ids} + Duplicates: {duplicates} + + You can fix this problem using `@pytest.mark.parametrize(..., ids=...)` or `pytest.param(..., id=...)`. + """).strip() # noqa: E501 + raise nodes.Collector.CollectError(msg) + # Map the ID to its next suffix. - id_suffixes: Dict[str, int] = defaultdict(int) + 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: - resolved_ids[index] = f"{id}{id_suffixes[id]}" + if id is HIDDEN_PARAM: + self._complain_multiple_hidden_parameter_sets() + 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]: + def _strict_parametrization_ids_enabled(self) -> bool: + if self.config is None: + return False + strict_parametrization_ids = self.config.getini("strict_parametrization_ids") + if strict_parametrization_ids is None: + strict_parametrization_ids = self.config.getini("strict") + return cast(bool, strict_parametrization_ids) + + def _resolve_ids(self) -> Iterable[str | _HiddenParam]: """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 + if parameterset.id is HIDDEN_PARAM: + yield HIDDEN_PARAM + else: + yield _ascii_escaped_by_config(parameterset.id, self.config) 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) + if self.ids[idx] is HIDDEN_PARAM: + yield HIDDEN_PARAM + else: + 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) + for val, argname in zip( + parameterset.values, self.argnames, strict=True + ) ) def _idval(self, val: object, argname: str, idx: int) -> str: @@ -1036,9 +1009,7 @@ class IdMaker: return idval return self._idval_from_argname(argname, idx) - def _idval_from_function( - self, val: object, argname: str, idx: int - ) -> Optional[str]: + 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: @@ -1054,24 +1025,24 @@ class IdMaker: return None return self._idval_from_value(id) - def _idval_from_hook(self, val: object, argname: str) -> Optional[str]: + 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: Optional[str] = self.config.hook.pytest_make_parametrize_id( + 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) -> Optional[str]: + 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, STRING_TYPES): + if isinstance(val, str | bytes): return _ascii_escaped_by_config(val, self.config) - elif val is None or isinstance(val, (float, int, bool, complex)): + elif val is None or isinstance(val, float | int | bool | complex): return str(val) - elif isinstance(val, Pattern): + elif isinstance(val, re.Pattern): return ascii_escaped(val.pattern) elif val is NOTSET: # Fallback to default. Note that NOTSET is an enum.Enum. @@ -1091,12 +1062,7 @@ class IdMaker: 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 = "" + prefix = self._make_error_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__." @@ -1109,6 +1075,21 @@ class IdMaker: and the index of the ParameterSet.""" return str(argname) + str(idx) + def _complain_multiple_hidden_parameter_sets(self) -> NoReturn: + fail( + f"{self._make_error_prefix()}multiple instances of HIDDEN_PARAM " + "cannot be used in the same parametrize call, " + "because the tests names need to be unique." + ) + + def _make_error_prefix(self) -> str: + if self.func_name is not None: + return f"In {self.func_name}: " + elif self.nodeid is not None: + return f"In {self.nodeid}: " + else: + return "" + @final @dataclasses.dataclass(frozen=True) @@ -1120,54 +1101,46 @@ class CallSpec2: and stored in item.callspec. """ - # arg name -> arg value which will be passed to the parametrized test - # function (direct parameterization). - funcargs: Dict[str, object] = dataclasses.field(default_factory=dict) - # arg name -> arg value which will be passed to a fixture of the same name - # (indirect parametrization). - params: Dict[str, object] = dataclasses.field(default_factory=dict) + # 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) + indices: dict[str, int] = dataclasses.field(default_factory=dict) + # arg name -> parameter scope. # Used for sorting parametrized resources. - _arg2scope: Dict[str, Scope] = dataclasses.field(default_factory=dict) + _arg2scope: Mapping[str, Scope] = dataclasses.field(default_factory=dict) # Parts which will be added to the item's name in `[..]` separated by "-". - _idlist: List[str] = dataclasses.field(default_factory=list) + _idlist: Sequence[str] = dataclasses.field(default_factory=tuple) # Marks which will be applied to the item. - marks: List[Mark] = dataclasses.field(default_factory=list) + marks: list[Mark] = dataclasses.field(default_factory=list) def setmulti( self, *, - valtypes: Mapping[str, "Literal['params', 'funcargs']"], argnames: Iterable[str], valset: Iterable[object], - id: str, - marks: Iterable[Union[Mark, MarkDecorator]], + id: str | _HiddenParam, + marks: Iterable[Mark | MarkDecorator], scope: Scope, param_index: int, - ) -> "CallSpec2": - funcargs = self.funcargs.copy() + nodeid: str, + ) -> CallSpec2: params = self.params.copy() indices = self.indices.copy() - arg2scope = self._arg2scope.copy() - for arg, val in zip(argnames, valset): - if arg in params or arg in funcargs: - raise ValueError(f"duplicate parametrization of {arg!r}") - valtype_for_arg = valtypes[arg] - if valtype_for_arg == "params": - params[arg] = val - elif valtype_for_arg == "funcargs": - funcargs[arg] = val - else: - assert_never(valtype_for_arg) + arg2scope = dict(self._arg2scope) + for arg, val in zip(argnames, valset, strict=True): + if arg in params: + raise nodes.Collector.CollectError( + f"{nodeid}: duplicate parametrization of {arg!r}" + ) + params[arg] = val indices[arg] = param_index arg2scope[arg] = scope return CallSpec2( - funcargs=funcargs, params=params, indices=indices, _arg2scope=arg2scope, - _idlist=[*self._idlist, id], + _idlist=self._idlist if id is HIDDEN_PARAM else [*self._idlist, id], marks=[*self.marks, *normalize_mark_list(marks)], ) @@ -1182,6 +1155,14 @@ class CallSpec2: 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. @@ -1193,7 +1174,7 @@ class Metafunc: def __init__( self, - definition: "FunctionDefinition", + definition: FunctionDefinition, fixtureinfo: fixtures.FuncFixtureInfo, config: Config, cls=None, @@ -1224,24 +1205,24 @@ class Metafunc: self._arg2fixturedefs = fixtureinfo.name2fixturedefs # Result of parametrize(). - self._calls: List[CallSpec2] = [] + self._calls: list[CallSpec2] = [] + + self._params_directness: dict[str, Literal["indirect", "direct"]] = {} def parametrize( self, - argnames: Union[str, Sequence[str]], - argvalues: Iterable[Union[ParameterSet, Sequence[object], object]], - indirect: Union[bool, Sequence[str]] = False, - ids: Optional[ - Union[Iterable[Optional[object]], Callable[[Any], Optional[object]]] - ] = None, - scope: "Optional[_ScopeName]" = None, + 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: Optional[Mark] = 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. + see about setting ``indirect`` to do it at test setup time instead. Can be called multiple times per test function (but only on different argument names), in which case each call parametrizes all previous @@ -1284,6 +1265,11 @@ class Metafunc: They are mapped to the corresponding index in ``argvalues``. ``None`` means to use the auto-generated id. + .. versionadded:: 8.4 + :ref:`hidden-param` means to hide the parameter set + from the test name. Can only be used at most 1 time, as + test names need to be unique. + 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 @@ -1300,6 +1286,8 @@ class Metafunc: It will also override any fixture-function defined scope, allowing to set a dynamic scope using test context or configuration. """ + nodeid = self.definition.nodeid + argnames, parametersets = ParameterSet._for_parametrize( argnames, argvalues, @@ -1311,7 +1299,7 @@ class Metafunc: if "request" in argnames: fail( - "'request' is a reserved name and cannot be used in @pytest.mark.parametrize", + f"{nodeid}: 'request' is a reserved name and cannot be used in @pytest.mark.parametrize", pytrace=False, ) @@ -1324,8 +1312,6 @@ class Metafunc: self._validate_if_using_arg_names(argnames, indirect) - arg_values_types = self._resolve_arg_value_types(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 @@ -1340,22 +1326,75 @@ class Metafunc: 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) + # Calculate directness. + arg_directness = self._resolve_args_directness(argnames, indirect) + self._params_directness.update(arg_directness) + + # Add direct parametrizations as fixturedefs to arg2fixturedefs by + # registering artificial "pseudo" FixtureDef's such that later at test + # setup time we can rely on FixtureDefs to exist for all argnames. + node = None + # For scopes higher than function, a "pseudo" FixtureDef might have + # already been created for the scope. We thus store and cache the + # FixtureDef on the node related to the scope. + if scope_ is Scope.Function: + name2pseudofixturedef = None + else: + 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}" + default: dict[str, FixtureDef[Any]] = {} + name2pseudofixturedef = node.stash.setdefault( + name2pseudofixturedef_key, default + ) + 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) + zip(ids, parametersets, strict=True) ): newcallspec = callspec.setmulti( - valtypes=arg_values_types, argnames=argnames, valset=param_set.values, id=param_id, marks=param_set.marks, scope=scope_, param_index=param_index, + nodeid=nodeid, ) newcalls.append(newcallspec) self._calls = newcalls @@ -1363,12 +1402,10 @@ class Metafunc: def _resolve_parameter_set_ids( self, argnames: Sequence[str], - ids: Optional[ - Union[Iterable[Optional[object]], Callable[[Any], Optional[object]]] - ], + ids: Iterable[object | None] | Callable[[Any], object | None] | None, parametersets: Sequence[ParameterSet], nodeid: str, - ) -> List[str]: + ) -> list[str | _HiddenParam]: """Resolve the actual ids for the given parameter sets. :param argnames: @@ -1406,10 +1443,10 @@ class Metafunc: def _validate_ids( self, - ids: Iterable[Optional[object]], + ids: Iterable[object | None], parametersets: Sequence[ParameterSet], func_name: str, - ) -> List[Optional[object]]: + ) -> list[object | None]: try: num_ids = len(ids) # type: ignore[arg-type] except TypeError: @@ -1426,50 +1463,49 @@ class Metafunc: return list(itertools.islice(ids, num_ids)) - def _resolve_arg_value_types( + def _resolve_args_directness( self, argnames: Sequence[str], - indirect: Union[bool, Sequence[str]], - ) -> Dict[str, "Literal['params', 'funcargs']"]: - """Resolve if each parametrized argument must be considered a - parameter to a fixture or a "funcarg" to the function, based on the - ``indirect`` parameter of the parametrized() call. + 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 List[str] argnames: List of argument names passed to ``parametrize()``. - :param indirect: Same as the ``indirect`` parameter of ``parametrize()``. - :rtype: Dict[str, str] - A dict mapping each arg name to either: - * "params" if the argname should be the parameter of a fixture of the same name. - * "funcargs" if the argname should be a parameter to the parametrized test function. + :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): - valtypes: Dict[str, Literal["params", "funcargs"]] = dict.fromkeys( - argnames, "params" if indirect else "funcargs" + arg_directness = dict.fromkeys( + argnames, "indirect" if indirect else "direct" ) elif isinstance(indirect, Sequence): - valtypes = dict.fromkeys(argnames, "funcargs") + arg_directness = dict.fromkeys(argnames, "direct") for arg in indirect: if arg not in argnames: fail( - "In {}: indirect fixture '{}' doesn't exist".format( - self.function.__name__, arg - ), + f"In {self.function.__name__}: indirect fixture '{arg}' doesn't exist", pytrace=False, ) - valtypes[arg] = "params" + arg_directness[arg] = "indirect" else: fail( - "In {func}: expected Sequence or boolean for indirect, got {type}".format( - type=type(indirect).__name__, func=self.function.__name__ - ), + f"In {self.function.__name__}: expected Sequence or boolean" + f" for indirect, got {type(indirect).__name__}", pytrace=False, ) - return valtypes + return arg_directness def _validate_if_using_arg_names( self, argnames: Sequence[str], - indirect: Union[bool, Sequence[str]], + indirect: bool | Sequence[str], ) -> None: """Check if all argnames are being used, by default values, or directly/indirectly. @@ -1483,9 +1519,7 @@ class Metafunc: if arg not in self.fixturenames: if arg in default_arg_names: fail( - "In {}: function already takes an argument '{}' with a default value".format( - func_name, arg - ), + f"In {func_name}: function already takes an argument '{arg}' with a default value", pytrace=False, ) else: @@ -1498,11 +1532,17 @@ class Metafunc: pytrace=False, ) + def _recompute_direct_params_indices(self) -> None: + for argname, param_type in self._params_directness.items(): + if param_type == "direct": + for i, callspec in enumerate(self._calls): + callspec.indices[argname] = i + def _find_parametrized_scope( argnames: Sequence[str], arg2fixturedefs: Mapping[str, Sequence[fixtures.FixtureDef[object]]], - indirect: Union[bool, Sequence[str]], + indirect: bool | Sequence[str], ) -> Scope: """Find the most appropriate scope for a parametrized call based on its arguments. @@ -1521,7 +1561,7 @@ def _find_parametrized_scope( if all_arguments_are_fixtures: fixturedefs = arg2fixturedefs or {} used_scopes = [ - fixturedef[0]._scope + fixturedef[-1]._scope for name, fixturedef in fixturedefs.items() if name in argnames ] @@ -1531,7 +1571,7 @@ def _find_parametrized_scope( return Scope.Function -def _ascii_escaped_by_config(val: Union[str, bytes], config: Optional[Config]) -> str: +def _ascii_escaped_by_config(val: str | bytes, config: Config | None) -> str: if config is None: escape_option = False else: @@ -1544,138 +1584,6 @@ def _ascii_escaped_by_config(val: Union[str, bytes], config: Optional[Config]) - return val if escape_option else ascii_escaped(val) # type: ignore -def _pretty_fixture_path(func) -> str: - cwd = Path.cwd() - loc = Path(getlocation(func, str(cwd))) - prefix = Path("...", "_pytest") - try: - return str(prefix / loc.relative_to(_PYTEST_DIR)) - except ValueError: - return bestrelpath(cwd, loc) - - -def show_fixtures_per_test(config): - from _pytest.main import wrap_session - - return wrap_session(config, _show_fixtures_per_test) - - -def _show_fixtures_per_test(config: Config, session: Session) -> None: - import _pytest.config - - session.perform_collect() - curdir = Path.cwd() - tw = _pytest.config.create_terminal_writer(config) - verbose = config.getvalue("verbose") - - def get_best_relpath(func) -> str: - loc = getlocation(func, str(curdir)) - return bestrelpath(curdir, Path(loc)) - - def write_fixture(fixture_def: fixtures.FixtureDef[object]) -> None: - argname = fixture_def.argname - if verbose <= 0 and argname.startswith("_"): - return - prettypath = _pretty_fixture_path(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")[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: Optional[FuncFixtureInfo] = 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) -> Union[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() - curdir = Path.cwd() - tw = _pytest.config.create_terminal_writer(config) - verbose = config.getvalue("verbose") - - 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, str(curdir)) - if (fixturedef.argname, loc) in seen: - continue - seen.add((fixturedef.argname, loc)) - available.append( - ( - len(fixturedef.baseid), - fixturedef.func.__module__, - _pretty_fixture_path(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(" [%s scope]" % fixturedef.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")[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) - - class Function(PyobjMixin, nodes.Item): """Item responsible for setting up and executing a Python test function. @@ -1687,7 +1595,7 @@ class Function(PyobjMixin, nodes.Item): :param config: The pytest Config object. :param callspec: - If given, this is function has been parametrized and the callspec contains + 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, @@ -1712,18 +1620,19 @@ class Function(PyobjMixin, nodes.Item): self, name: str, parent, - config: Optional[Config] = None, - callspec: Optional[CallSpec2] = None, + config: Config | None = None, + callspec: CallSpec2 | None = None, callobj=NOTSET, - keywords: Optional[Mapping[str, Any]] = None, - session: Optional[Session] = None, - fixtureinfo: Optional[FuncFixtureInfo] = None, - originalname: Optional[str] = None, + 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._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 @@ -1752,33 +1661,52 @@ class Function(PyobjMixin, nodes.Item): self.keywords.update(keywords) if fixtureinfo is None: - fixtureinfo = self.session._fixturemanager.getfixtureinfo( - self, self.obj, self.cls, funcargs=True - ) + 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): # todo: determine sound type limitations + 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.FixtureRequest(self, _ispytest=True) + self.funcargs: dict[str, object] = {} + self._request = fixtures.TopRequest(self, _ispytest=True) @property def function(self): """Underlying python 'function' object.""" return getimfunc(self.obj) - def _getobj(self): - assert self.parent is not None + @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. - parent_obj = self.parent.newinstance() + 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) @@ -1813,10 +1741,11 @@ class Function(PyobjMixin, nodes.Item): if self.config.getoption("tbstyle", "auto") == "auto": if len(ntraceback) > 2: ntraceback = Traceback( - entry - if i == 0 or i == len(ntraceback) - 1 - else entry.with_repr_style("short") - for i, entry in enumerate(ntraceback) + ( + ntraceback[0], + *(t.with_repr_style("short") for t in ntraceback[1:-1]), + ntraceback[-1], + ) ) return ntraceback @@ -1826,7 +1755,7 @@ class Function(PyobjMixin, nodes.Item): def repr_failure( # type: ignore[override] self, excinfo: ExceptionInfo[BaseException], - ) -> Union[str, TerminalRepr]: + ) -> str | TerminalRepr: style = self.config.getoption("tbstyle", "auto") if style == "auto": style = "long" diff --git a/venv/lib/python3.10/site-packages/_pytest/python_api.py b/venv/lib/python3.10/site-packages/_pytest/python_api.py index 1833561..1e389eb 100644 --- a/venv/lib/python3.10/site-packages/_pytest/python_api.py +++ b/venv/lib/python3.10/site-packages/_pytest/python_api.py @@ -1,53 +1,31 @@ -import math -import pprint +# mypy: allow-untyped-defs +from __future__ import annotations + from collections.abc import Collection +from collections.abc import Mapping +from collections.abc import Sequence from collections.abc import Sized from decimal import Decimal +import math from numbers import Complex -from types import TracebackType +import pprint +import sys from typing import Any -from typing import Callable -from typing import cast -from typing import ContextManager -from typing import List -from typing import Mapping -from typing import Optional -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 -from typing import Union + if TYPE_CHECKING: from numpy import ndarray -import _pytest._code -from _pytest.compat import final -from _pytest.compat import STRING_TYPES -from _pytest.compat import overload -from _pytest.outcomes import fail - - -def _non_numeric_type_error(value, at: Optional[str]) -> TypeError: - at_str = f" at {at}" if at else "" - return TypeError( - "cannot make approximate comparisons to non-numeric values: {!r} {}".format( - value, at_str - ) - ) - - def _compare_approx( full_object: object, - message_data: Sequence[Tuple[str, str, str]], + 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]: +) -> list[str]: message_list = list(message_data) message_list.insert(0, ("Index", "Obtained", "Expected")) max_sizes = [0, 0, 0] @@ -88,7 +66,7 @@ class ApproxBase: def __repr__(self) -> str: raise NotImplementedError - def _repr_compare(self, other_side: Any) -> List[str]: + def _repr_compare(self, other_side: Any) -> list[str]: return [ "comparison failed", f"Obtained: {other_side}", @@ -112,7 +90,7 @@ class ApproxBase: def __ne__(self, actual) -> bool: return not (actual == self) - def _approx_scalar(self, x) -> "ApproxScalar": + 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) @@ -135,9 +113,11 @@ class ApproxBase: def _recursive_sequence_map(f, x): """Recursively map a function over a sequence of arbitrary depth""" - if isinstance(x, (list, tuple)): + 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) @@ -151,12 +131,12 @@ class ApproxNumpy(ApproxBase): ) return f"approx({list_scalars!r})" - def _repr_compare(self, other_side: "ndarray") -> List[str]: + 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, ...] + 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. @@ -172,10 +152,14 @@ class ApproxNumpy(ApproxBase): self._approx_scalar, self.expected.tolist() ) - if np_array_shape != other_side.shape: + # 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.shape}", + f"Shapes: {np_array_shape} and {other_side_as_array.shape}", ] number_of_elements = self.expected.size @@ -184,7 +168,7 @@ class ApproxNumpy(ApproxBase): 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, 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) @@ -197,7 +181,7 @@ class ApproxNumpy(ApproxBase): message_data = [ ( str(index), - str(get_value_from_nested_list(other_side, 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 @@ -247,13 +231,23 @@ class ApproxMapping(ApproxBase): with numeric values (the keys can be anything).""" def __repr__(self) -> str: - return "approx({!r})".format( - {k: self._approx_scalar(v) for k, v in self.expected.items()} - ) + 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]: + def _repr_compare(self, other_side: Mapping[object, float]) -> list[str]: import math + if len(self.expected) != len(other_side): + return [ + "Impossible to compare mappings with different sizes.", + f"Lengths: {len(self.expected)} and {len(other_side)}", + ] + + if set(self.expected.keys()) != set(other_side.keys()): + return [ + "comparison failed.", + f"Mappings has different keys: expected {self.expected.keys()} but got {other_side.keys()}", + ] + approx_side_as_map = { k: self._approx_scalar(v) for k, v in self.expected.items() } @@ -263,23 +257,26 @@ class ApproxMapping(ApproxBase): max_rel_diff = -math.inf different_ids = [] for (approx_key, approx_value), other_value in zip( - approx_side_as_map.items(), other_side.values() + approx_side_as_map.items(), other_side.values(), strict=True ): if approx_value != other_value: if approx_value.expected is not None and other_value is not None: - 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 - ), + 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 = [ @@ -324,11 +321,9 @@ class ApproxSequenceLike(ApproxBase): seq_type = type(self.expected) if seq_type not in (tuple, list): seq_type = list - return "approx({!r})".format( - seq_type(self._approx_scalar(x) for x in self.expected) - ) + 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]: + def _repr_compare(self, other_side: Sequence[float]) -> list[str]: import math if len(self.expected) != len(other_side): @@ -344,17 +339,21 @@ class ApproxSequenceLike(ApproxBase): max_rel_diff = -math.inf different_ids = [] for i, (approx_value, other_value) in enumerate( - zip(approx_side_as_map, other_side) + zip(approx_side_as_map, other_side, strict=True) ): 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 + try: + abs_diff = abs(approx_value.expected - other_value) + max_abs_diff = max(max_abs_diff, abs_diff) + # Ignore non-numbers for the diff calculations (#13012). + except TypeError: + pass else: - max_rel_diff = max(max_rel_diff, abs_diff / abs(other_value)) + 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 @@ -378,7 +377,7 @@ class ApproxSequenceLike(ApproxBase): return super().__eq__(actual) def _yield_comparisons(self, actual): - return zip(actual, self.expected) + return zip(actual, self.expected, strict=True) def _check_type(self) -> None: __tracebackhide__ = True @@ -393,8 +392,8 @@ class ApproxScalar(ApproxBase): # Using Real should be better than this Union, but not possible yet: # https://github.com/python/typeshed/pull/3108 - DEFAULT_ABSOLUTE_TOLERANCE: Union[float, Decimal] = 1e-12 - DEFAULT_RELATIVE_TOLERANCE: Union[float, Decimal] = 1e-6 + 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 @@ -405,15 +404,21 @@ class ApproxScalar(ApproxBase): # 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 (not isinstance(self.expected, (Complex, Decimal))) or math.isinf( - abs(self.expected) # type: ignore[arg-type] + 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 1e-3 <= self.tolerance < 1e3: + vetted_tolerance = f"{self.tolerance:n}" + else: + vetted_tolerance = f"{self.tolerance:.1e}" + if ( isinstance(self.expected, Complex) and self.expected.imag @@ -428,30 +433,42 @@ class ApproxScalar(ApproxBase): def __eq__(self, actual) -> bool: """Return whether the given value is equal to the expected value within the pre-specified tolerance.""" + + def is_bool(val: Any) -> bool: + # Check if `val` is a native bool or numpy bool. + if isinstance(val, bool): + return True + if np := sys.modules.get("numpy"): + return isinstance(val, np.bool_) + return False + 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. - if actual == self.expected: + # Short-circuit exact equality, except for bool and np.bool_ + if is_bool(self.expected) and not is_bool(actual): + 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. - if not ( - isinstance(self.expected, (Complex, Decimal)) - and isinstance(actual, (Complex, Decimal)) + # __sub__, and __float__ are defined. Also, consider bool to be + # non-numeric, even though it has the required arithmetic. + if is_bool(self.expected) 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)): # type: ignore[arg-type] - return self.nan_ok and math.isnan(abs(actual)) # type: ignore[arg-type] + 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 @@ -459,15 +476,14 @@ class ApproxScalar(ApproxBase): # 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)): # type: ignore[arg-type] + 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 + __hash__ = None @property def tolerance(self): @@ -523,6 +539,25 @@ class ApproxDecimal(ApproxScalar): DEFAULT_ABSOLUTE_TOLERANCE = Decimal("1e-12") DEFAULT_RELATIVE_TOLERANCE = Decimal("1e-6") + def __repr__(self) -> str: + if isinstance(self.rel, float): + rel = Decimal.from_float(self.rel) + else: + rel = self.rel + + if isinstance(self.abs, float): + abs_ = Decimal.from_float(self.abs) + else: + abs_ = self.abs + + tol_str = "???" + if rel is not None and Decimal("1e-3") <= rel <= Decimal("1e3"): + tol_str = f"{rel:.1e}" + elif abs_ is not None: + tol_str = f"{abs_:.1e}" + + return f"{self.expected} ± {tol_str}" + 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 @@ -624,8 +659,10 @@ def approx(expected, rel=None, abs=None, nan_ok: bool = False) -> ApproxBase: >>> 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 + **Non-numeric types** + + You can also use ``approx`` to compare non-numeric types, or dicts and + sequences containing non-numeric types, in which case it falls back to strict equality. This can be useful for comparing dicts and sequences that can contain optional values:: @@ -682,6 +719,15 @@ def approx(expected, rel=None, abs=None, nan_ok: bool = False) -> ApproxBase: from the `re_assert package `_. + + .. note:: + + Unlike built-in equality, this function considers + booleans unequal to numeric zero or one. For example:: + + >>> 1 == approx(True) + False + .. warning:: .. versionchanged:: 3.2 @@ -700,13 +746,12 @@ def approx(expected, rel=None, abs=None, nan_ok: bool = False) -> ApproxBase: .. versionchanged:: 3.7.1 ``approx`` raises ``TypeError`` when it encounters a dict value or - sequence element of nonnumeric type. + sequence element of non-numeric type. .. versionchanged:: 6.1.0 - ``approx`` falls back to strict equality for nonnumeric types instead + ``approx`` falls back to strict equality for non-numeric 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). # @@ -725,25 +770,16 @@ def approx(expected, rel=None, abs=None, nan_ok: bool = False) -> ApproxBase: __tracebackhide__ = True if isinstance(expected, Decimal): - cls: Type[ApproxBase] = ApproxDecimal + cls: type[ApproxBase] = ApproxDecimal elif isinstance(expected, Mapping): cls = ApproxMapping elif _is_numpy_array(expected): expected = _as_numpy_array(expected) cls = ApproxNumpy - elif ( - hasattr(expected, "__getitem__") - and isinstance(expected, Sized) - # Type ignored because the error is wrong -- not unreachable. - and not isinstance(expected, STRING_TYPES) # type: ignore[unreachable] - ): + elif _is_sequence_like(expected): cls = ApproxSequenceLike - elif ( - isinstance(expected, Collection) - # Type ignored because the error is wrong -- not unreachable. - and not isinstance(expected, STRING_TYPES) # type: ignore[unreachable] - ): - msg = f"pytest.approx() only supports ordered sequences, but got: {repr(expected)}" + 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 @@ -751,6 +787,14 @@ def approx(expected, rel=None, abs=None, nan_ok: bool = False) -> ApproxBase: 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, @@ -759,13 +803,11 @@ def _is_numpy_array(obj: object) -> bool: return _as_numpy_array(obj) is not None -def _as_numpy_array(obj: object) -> Optional["ndarray"]: +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__ @@ -776,221 +818,3 @@ def _as_numpy_array(obj: object) -> Optional["ndarray"]: 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: Union[Type[E], Tuple[Type[E], ...]], - *, - match: Optional[Union[str, Pattern[str]]] = ..., -) -> "RaisesContext[E]": - ... - - -@overload -def raises( # noqa: F811 - expected_exception: Union[Type[E], Tuple[Type[E], ...]], - func: Callable[..., Any], - *args: Any, - **kwargs: Any, -) -> _pytest._code.ExceptionInfo[E]: - ... - - -def raises( # noqa: F811 - expected_exception: Union[Type[E], Tuple[Type[E], ...]], *args: Any, **kwargs: Any -) -> Union["RaisesContext[E]", _pytest._code.ExceptionInfo[E]]: - r"""Assert that a code block/function call raises an exception. - - :param typing.Type[E] | typing.Tuple[typing.Type[E], ...] expected_exception: - The expected exception type, or a tuple if one of multiple possible - exception types are expected. - :kwparam str | typing.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 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 :py:func:`pytest.raises` is used as a context manager, - and passed through to the function otherwise. - When using :py:func:`pytest.raises` as a function, you can use: - ``pytest.raises(Exc, func, match="passed on").match("my pattern")``.) - - .. currentmodule:: _pytest._code - - Use ``pytest.raises`` as a context manager, which will capture the exception of the given - type:: - - >>> import pytest - >>> with pytest.raises(ZeroDivisionError): - ... 1/0 - - If the code block does not raise the expected exception (``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 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" - - .. 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. - - **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: Optional[Union[str, Pattern[str]]] = 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: Union[Type[E], Tuple[Type[E], ...]], - message: str, - match_expr: Optional[Union[str, Pattern[str]]] = None, - ) -> None: - self.expected_exception = expected_exception - self.message = message - self.match_expr = match_expr - self.excinfo: Optional[_pytest._code.ExceptionInfo[E]] = None - - def __enter__(self) -> _pytest._code.ExceptionInfo[E]: - self.excinfo = _pytest._code.ExceptionInfo.for_later() - return self.excinfo - - def __exit__( - self, - exc_type: Optional[Type[BaseException]], - exc_val: Optional[BaseException], - exc_tb: Optional[TracebackType], - ) -> 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/python3.10/site-packages/_pytest/python_path.py b/venv/lib/python3.10/site-packages/_pytest/python_path.py deleted file mode 100644 index cceabbc..0000000 --- a/venv/lib/python3.10/site-packages/_pytest/python_path.py +++ /dev/null @@ -1,24 +0,0 @@ -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/python3.10/site-packages/_pytest/raises.py b/venv/lib/python3.10/site-packages/_pytest/raises.py new file mode 100644 index 0000000..7c246fd --- /dev/null +++ b/venv/lib/python3.10/site-packages/_pytest/raises.py @@ -0,0 +1,1517 @@ +from __future__ import annotations + +from abc import ABC +from abc import abstractmethod +import re +from re import Pattern +import sys +from textwrap import indent +from typing import Any +from typing import cast +from typing import final +from typing import Generic +from typing import get_args +from typing import get_origin +from typing import Literal +from typing import overload +from typing import TYPE_CHECKING +import warnings + +from _pytest._code import ExceptionInfo +from _pytest._code.code import stringify_exception +from _pytest.outcomes import fail +from _pytest.warning_types import PytestWarning + + +if TYPE_CHECKING: + from collections.abc import Callable + from collections.abc import Sequence + + # for some reason Sphinx does not play well with 'from types import TracebackType' + import types + from typing import TypeGuard + + from typing_extensions import ParamSpec + from typing_extensions import TypeVar + + P = ParamSpec("P") + + # this conditional definition is because we want to allow a TypeVar default + BaseExcT_co_default = TypeVar( + "BaseExcT_co_default", + bound=BaseException, + default=BaseException, + covariant=True, + ) + + # Use short name because it shows up in docs. + E = TypeVar("E", bound=BaseException, default=BaseException) +else: + from typing import TypeVar + + BaseExcT_co_default = TypeVar( + "BaseExcT_co_default", bound=BaseException, covariant=True + ) + +# RaisesGroup doesn't work with a default. +BaseExcT_co = TypeVar("BaseExcT_co", bound=BaseException, covariant=True) +BaseExcT_1 = TypeVar("BaseExcT_1", bound=BaseException) +BaseExcT_2 = TypeVar("BaseExcT_2", bound=BaseException) +ExcT_1 = TypeVar("ExcT_1", bound=Exception) +ExcT_2 = TypeVar("ExcT_2", bound=Exception) + +if sys.version_info < (3, 11): + from exceptiongroup import BaseExceptionGroup + from exceptiongroup import ExceptionGroup + + +# String patterns default to including the unicode flag. +_REGEX_NO_FLAGS = re.compile(r"").flags + + +# pytest.raises helper +@overload +def raises( + expected_exception: type[E] | tuple[type[E], ...], + *, + match: str | re.Pattern[str] | None = ..., + check: Callable[[E], bool] = ..., +) -> RaisesExc[E]: ... + + +@overload +def raises( + *, + match: str | re.Pattern[str], + # If exception_type is not provided, check() must do any typechecks itself. + check: Callable[[BaseException], bool] = ..., +) -> RaisesExc[BaseException]: ... + + +@overload +def raises(*, check: Callable[[BaseException], bool]) -> RaisesExc[BaseException]: ... + + +@overload +def raises( + expected_exception: type[E] | tuple[type[E], ...], + func: Callable[..., Any], + *args: Any, + **kwargs: Any, +) -> ExceptionInfo[E]: ... + + +def raises( + expected_exception: type[E] | tuple[type[E], ...] | None = None, + *args: Any, + **kwargs: Any, +) -> RaisesExc[BaseException] | 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. + + This is not a required parameter, you may opt to only use ``match`` and/or + ``check`` for verifying the raised exception. + + :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")``.) + + :kwparam Callable[[BaseException], bool] check: + + .. versionadded:: 8.4 + + If specified, a callable that will be called with the exception as a parameter + after checking the type and the match regex if specified. + If it returns ``True`` it will be considered a match, if not it will + be considered a failed match. + + + 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 ``check`` argument, if provided, must return True when passed the raised exception + for the match to be successful, otherwise an :exc:`AssertionError` is raised. + + >>> import errno + >>> with pytest.raises(OSError, check=lambda e: e.errno == errno.EACCES): + ... raise OSError(errno.EACCES, "no permission to view") + + 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:: + + # Careful, this will catch ANY exception raised. + with pytest.raises(Exception): + 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 + + **Expecting exception groups** + + When expecting exceptions wrapped in :exc:`BaseExceptionGroup` or + :exc:`ExceptionGroup`, you should instead use :class:`pytest.RaisesGroup`. + + **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 args: + if set(kwargs) - {"match", "check", "expected_exception"}: + msg = "Unexpected keyword arguments passed to pytest.raises: " + msg += ", ".join(sorted(kwargs)) + msg += "\nUse context-manager form instead?" + raise TypeError(msg) + + if expected_exception is None: + return RaisesExc(**kwargs) + return RaisesExc(expected_exception, **kwargs) + + 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'." + ) + func = args[0] + if not callable(func): + raise TypeError(f"{func!r} object (type: {type(func)}) must be callable") + with RaisesExc(expected_exception) as excinfo: + func(*args[1:], **kwargs) + try: + return excinfo + finally: + del excinfo + + +# note: RaisesExc/RaisesGroup uses fail() internally, so this alias +# indicates (to [internal] plugins?) that `pytest.raises` will +# raise `_pytest.outcomes.Failed`, where +# `outcomes.Failed is outcomes.fail.Exception is raises.Exception` +# note: this is *not* the same as `_pytest.main.Failed` +# note: mypy does not recognize this attribute, and it's not possible +# to use a protocol/decorator like the others in outcomes due to +# https://github.com/python/mypy/issues/18715 +raises.Exception = fail.Exception # type: ignore[attr-defined] + + +def _match_pattern(match: Pattern[str]) -> str | Pattern[str]: + """Helper function to remove redundant `re.compile` calls when printing regex""" + return match.pattern if match.flags == _REGEX_NO_FLAGS else match + + +def repr_callable(fun: Callable[[BaseExcT_1], bool]) -> str: + """Get the repr of a ``check`` parameter. + + Split out so it can be monkeypatched (e.g. by hypothesis) + """ + return repr(fun) + + +def backquote(s: str) -> str: + return "`" + s + "`" + + +def _exception_type_name( + e: type[BaseException] | tuple[type[BaseException], ...], +) -> str: + if isinstance(e, type): + return e.__name__ + if len(e) == 1: + return e[0].__name__ + return "(" + ", ".join(ee.__name__ for ee in e) + ")" + + +def _check_raw_type( + expected_type: type[BaseException] | tuple[type[BaseException], ...] | None, + exception: BaseException, +) -> str | None: + if expected_type is None or expected_type == (): + return None + + if not isinstance( + exception, + expected_type, + ): + actual_type_str = backquote(_exception_type_name(type(exception)) + "()") + expected_type_str = backquote(_exception_type_name(expected_type)) + if ( + isinstance(exception, BaseExceptionGroup) + and isinstance(expected_type, type) + and not issubclass(expected_type, BaseExceptionGroup) + ): + return f"Unexpected nested {actual_type_str}, expected {expected_type_str}" + return f"{actual_type_str} is not an instance of {expected_type_str}" + return None + + +def is_fully_escaped(s: str) -> bool: + # we know we won't compile with re.VERBOSE, so whitespace doesn't need to be escaped + metacharacters = "{}()+.*?^$[]" + return not any( + c in metacharacters and (i == 0 or s[i - 1] != "\\") for (i, c) in enumerate(s) + ) + + +def unescape(s: str) -> str: + return re.sub(r"\\([{}()+-.*?^$\[\]\s\\])", r"\1", s) + + +# These classes conceptually differ from ExceptionInfo in that ExceptionInfo is tied, and +# constructed from, a particular exception - whereas these are constructed with expected +# exceptions, and later allow matching towards particular exceptions. +# But there's overlap in `ExceptionInfo.match` and `AbstractRaises._check_match`, as with +# `AbstractRaises.matches` and `ExceptionInfo.errisinstance`+`ExceptionInfo.group_contains`. +# The interaction between these classes should perhaps be improved. +class AbstractRaises(ABC, Generic[BaseExcT_co]): + """ABC with common functionality shared between RaisesExc and RaisesGroup""" + + def __init__( + self, + *, + match: str | Pattern[str] | None, + check: Callable[[BaseExcT_co], bool] | None, + ) -> None: + if isinstance(match, str): + # juggle error in order to avoid context to fail (necessary?) + re_error = None + try: + self.match: Pattern[str] | None = re.compile(match) + except re.error as e: + re_error = e + if re_error is not None: + fail(f"Invalid regex pattern provided to 'match': {re_error}") + if match == "": + warnings.warn( + PytestWarning( + "matching against an empty string will *always* pass. If you want " + "to check for an empty message you need to pass '^$'. If you don't " + "want to match you should pass `None` or leave out the parameter." + ), + stacklevel=2, + ) + else: + self.match = match + + # check if this is a fully escaped regex and has ^$ to match fully + # in which case we can do a proper diff on error + self.rawmatch: str | None = None + if isinstance(match, str) or ( + isinstance(match, Pattern) and match.flags == _REGEX_NO_FLAGS + ): + if isinstance(match, Pattern): + match = match.pattern + if ( + match + and match[0] == "^" + and match[-1] == "$" + and is_fully_escaped(match[1:-1]) + ): + self.rawmatch = unescape(match[1:-1]) + + self.check = check + self._fail_reason: str | None = None + + # used to suppress repeated printing of `repr(self.check)` + self._nested: bool = False + + # set in self._parse_exc + self.is_baseexception = False + + def _parse_exc( + self, exc: type[BaseExcT_1] | types.GenericAlias, expected: str + ) -> type[BaseExcT_1]: + if isinstance(exc, type) and issubclass(exc, BaseException): + if not issubclass(exc, Exception): + self.is_baseexception = True + return exc + # because RaisesGroup does not support variable number of exceptions there's + # still a use for RaisesExc(ExceptionGroup[Exception]). + origin_exc: type[BaseException] | None = get_origin(exc) + if origin_exc and issubclass(origin_exc, BaseExceptionGroup): + exc_type = get_args(exc)[0] + if ( + issubclass(origin_exc, ExceptionGroup) and exc_type in (Exception, Any) + ) or ( + issubclass(origin_exc, BaseExceptionGroup) + and exc_type in (BaseException, Any) + ): + if not issubclass(origin_exc, ExceptionGroup): + self.is_baseexception = True + return cast(type[BaseExcT_1], origin_exc) + else: + raise ValueError( + f"Only `ExceptionGroup[Exception]` or `BaseExceptionGroup[BaseException]` " + f"are accepted as generic types but got `{exc}`. " + f"As `raises` will catch all instances of the specified group regardless of the " + f"generic argument specific nested exceptions has to be checked " + f"with `RaisesGroup`." + ) + # unclear if the Type/ValueError distinction is even helpful here + msg = f"Expected {expected}, but got " + if isinstance(exc, type): # type: ignore[unreachable] + raise ValueError(msg + f"{exc.__name__!r}") + if isinstance(exc, BaseException): # type: ignore[unreachable] + raise TypeError(msg + f"an exception instance: {type(exc).__name__}") + raise TypeError(msg + repr(type(exc).__name__)) + + @property + def fail_reason(self) -> str | None: + """Set after a call to :meth:`matches` to give a human-readable reason for why the match failed. + When used as a context manager the string will be printed as the reason for the + test failing.""" + return self._fail_reason + + def _check_check( + self: AbstractRaises[BaseExcT_1], + exception: BaseExcT_1, + ) -> bool: + if self.check is None: + return True + + if self.check(exception): + return True + + check_repr = "" if self._nested else " " + repr_callable(self.check) + self._fail_reason = f"check{check_repr} did not return True" + return False + + # TODO: harmonize with ExceptionInfo.match + def _check_match(self, e: BaseException) -> bool: + if self.match is None or re.search( + self.match, + stringified_exception := stringify_exception( + e, include_subexception_msg=False + ), + ): + return True + + # if we're matching a group, make sure we're explicit to reduce confusion + # if they're trying to match an exception contained within the group + maybe_specify_type = ( + f" the `{_exception_type_name(type(e))}()`" + if isinstance(e, BaseExceptionGroup) + else "" + ) + if isinstance(self.rawmatch, str): + # TODO: it instructs to use `-v` to print leading text, but that doesn't work + # I also don't know if this is the proper entry point, or tool to use at all + from _pytest.assertion.util import _diff_text + from _pytest.assertion.util import dummy_highlighter + + diff = _diff_text(self.rawmatch, stringified_exception, dummy_highlighter) + self._fail_reason = ("\n" if diff[0][0] == "-" else "") + "\n".join(diff) + return False + + self._fail_reason = ( + f"Regex pattern did not match{maybe_specify_type}.\n" + f" Expected regex: {_match_pattern(self.match)!r}\n" + f" Actual message: {stringified_exception!r}" + ) + if _match_pattern(self.match) == stringified_exception: + self._fail_reason += "\n Did you mean to `re.escape()` the regex?" + return False + + @abstractmethod + def matches( + self: AbstractRaises[BaseExcT_1], exception: BaseException + ) -> TypeGuard[BaseExcT_1]: + """Check if an exception matches the requirements of this AbstractRaises. + If it fails, :meth:`AbstractRaises.fail_reason` should be set. + """ + + +@final +class RaisesExc(AbstractRaises[BaseExcT_co_default]): + """ + .. versionadded:: 8.4 + + + This is the class constructed when calling :func:`pytest.raises`, but may be used + directly as a helper class with :class:`RaisesGroup` when you want to specify + requirements on sub-exceptions. + + You don't need this if you only want to specify the type, since :class:`RaisesGroup` + accepts ``type[BaseException]``. + + :param type[BaseException] | tuple[type[BaseException]] | None expected_exception: + The expected type, or one of several possible types. + May be ``None`` in order to only make use of ``match`` and/or ``check`` + + The type is checked with :func:`isinstance`, and does not need to be an exact match. + If that is wanted you can use the ``check`` parameter. + + :kwparam str | Pattern[str] match: + A regex to match. + + :kwparam Callable[[BaseException], bool] check: + If specified, a callable that will be called with the exception as a parameter + after checking the type and the match regex if specified. + If it returns ``True`` it will be considered a match, if not it will + be considered a failed match. + + :meth:`RaisesExc.matches` can also be used standalone to check individual exceptions. + + Examples:: + + with RaisesGroup(RaisesExc(ValueError, match="string")) + ... + with RaisesGroup(RaisesExc(check=lambda x: x.args == (3, "hello"))): + ... + with RaisesGroup(RaisesExc(check=lambda x: type(x) is ValueError)): + ... + """ + + # Trio bundled hypothesis monkeypatching, we will probably instead assume that + # hypothesis will handle that in their pytest plugin by the time this is released. + # Alternatively we could add a version of get_pretty_function_description ourselves + # https://github.com/HypothesisWorks/hypothesis/blob/8ced2f59f5c7bea3344e35d2d53e1f8f8eb9fcd8/hypothesis-python/src/hypothesis/internal/reflection.py#L439 + + # At least one of the three parameters must be passed. + @overload + def __init__( + self, + expected_exception: ( + type[BaseExcT_co_default] | tuple[type[BaseExcT_co_default], ...] + ), + /, + *, + match: str | Pattern[str] | None = ..., + check: Callable[[BaseExcT_co_default], bool] | None = ..., + ) -> None: ... + + @overload + def __init__( + self: RaisesExc[BaseException], # Give E a value. + /, + *, + match: str | Pattern[str] | None, + # If exception_type is not provided, check() must do any typechecks itself. + check: Callable[[BaseException], bool] | None = ..., + ) -> None: ... + + @overload + def __init__(self, /, *, check: Callable[[BaseException], bool]) -> None: ... + + def __init__( + self, + expected_exception: ( + type[BaseExcT_co_default] | tuple[type[BaseExcT_co_default], ...] | None + ) = None, + /, + *, + match: str | Pattern[str] | None = None, + check: Callable[[BaseExcT_co_default], bool] | None = None, + ): + super().__init__(match=match, check=check) + if isinstance(expected_exception, tuple): + expected_exceptions = expected_exception + elif expected_exception is None: + expected_exceptions = () + else: + expected_exceptions = (expected_exception,) + + if (expected_exceptions == ()) and match is None and check is None: + raise ValueError("You must specify at least one parameter to match on.") + + self.expected_exceptions = tuple( + self._parse_exc(e, expected="a BaseException type") + for e in expected_exceptions + ) + + self._just_propagate = False + + def matches( + self, + exception: BaseException | None, + ) -> TypeGuard[BaseExcT_co_default]: + """Check if an exception matches the requirements of this :class:`RaisesExc`. + If it fails, :attr:`RaisesExc.fail_reason` will be set. + + Examples:: + + assert RaisesExc(ValueError).matches(my_exception): + # is equivalent to + assert isinstance(my_exception, ValueError) + + # this can be useful when checking e.g. the ``__cause__`` of an exception. + with pytest.raises(ValueError) as excinfo: + ... + assert RaisesExc(SyntaxError, match="foo").matches(excinfo.value.__cause__) + # above line is equivalent to + assert isinstance(excinfo.value.__cause__, SyntaxError) + assert re.search("foo", str(excinfo.value.__cause__) + + """ + self._just_propagate = False + if exception is None: + self._fail_reason = "exception is None" + return False + if not self._check_type(exception): + self._just_propagate = True + return False + + if not self._check_match(exception): + return False + + return self._check_check(exception) + + def __repr__(self) -> str: + parameters = [] + if self.expected_exceptions: + parameters.append(_exception_type_name(self.expected_exceptions)) + if self.match is not None: + # If no flags were specified, discard the redundant re.compile() here. + parameters.append( + f"match={_match_pattern(self.match)!r}", + ) + if self.check is not None: + parameters.append(f"check={repr_callable(self.check)}") + return f"RaisesExc({', '.join(parameters)})" + + def _check_type(self, exception: BaseException) -> TypeGuard[BaseExcT_co_default]: + self._fail_reason = _check_raw_type(self.expected_exceptions, exception) + return self._fail_reason is None + + def __enter__(self) -> ExceptionInfo[BaseExcT_co_default]: + self.excinfo: ExceptionInfo[BaseExcT_co_default] = ExceptionInfo.for_later() + return self.excinfo + + # TODO: move common code into superclass + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: types.TracebackType | None, + ) -> bool: + __tracebackhide__ = True + if exc_type is None: + if not self.expected_exceptions: + fail("DID NOT RAISE any exception") + if len(self.expected_exceptions) > 1: + fail(f"DID NOT RAISE any of {self.expected_exceptions!r}") + + fail(f"DID NOT RAISE {self.expected_exceptions[0]!r}") + + assert self.excinfo is not None, ( + "Internal error - should have been constructed in __enter__" + ) + + if not self.matches(exc_val): + if self._just_propagate: + return False + raise AssertionError(self._fail_reason) + + # Cast to narrow the exception type now that it's verified.... + # even though the TypeGuard in self.matches should be narrowing + exc_info = cast( + "tuple[type[BaseExcT_co_default], BaseExcT_co_default, types.TracebackType]", + (exc_type, exc_val, exc_tb), + ) + self.excinfo.fill_unfilled(exc_info) + return True + + +@final +class RaisesGroup(AbstractRaises[BaseExceptionGroup[BaseExcT_co]]): + """ + .. versionadded:: 8.4 + + Contextmanager for checking for an expected :exc:`ExceptionGroup`. + This works similar to :func:`pytest.raises`, but allows for specifying the structure of an :exc:`ExceptionGroup`. + :meth:`ExceptionInfo.group_contains` also tries to handle exception groups, + but it is very bad at checking that you *didn't* get unexpected exceptions. + + The catching behaviour differs from :ref:`except* `, being much + stricter about the structure by default. + By using ``allow_unwrapped=True`` and ``flatten_subgroups=True`` you can match + :ref:`except* ` fully when expecting a single exception. + + :param args: + Any number of exception types, :class:`RaisesGroup` or :class:`RaisesExc` + to specify the exceptions contained in this exception. + All specified exceptions must be present in the raised group, *and no others*. + + If you expect a variable number of exceptions you need to use + :func:`pytest.raises(ExceptionGroup) ` and manually check + the contained exceptions. Consider making use of :meth:`RaisesExc.matches`. + + It does not care about the order of the exceptions, so + ``RaisesGroup(ValueError, TypeError)`` + is equivalent to + ``RaisesGroup(TypeError, ValueError)``. + :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 group 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`. + + Note that " (5 subgroups)" will be stripped from the ``repr`` before matching. + :kwparam Callable[[E], bool] check: + If specified, a callable that will be called with the group as a parameter + after successfully matching the expected exceptions. If it returns ``True`` + it will be considered a match, if not it will be considered a failed match. + :kwparam bool allow_unwrapped: + If expecting a single exception or :class:`RaisesExc` it will match even + if the exception is not inside an exceptiongroup. + + Using this together with ``match``, ``check`` or expecting multiple exceptions + will raise an error. + :kwparam bool flatten_subgroups: + "flatten" any groups inside the raised exception group, extracting all exceptions + inside any nested groups, before matching. Without this it expects you to + fully specify the nesting structure by passing :class:`RaisesGroup` as expected + parameter. + + Examples:: + + with RaisesGroup(ValueError): + raise ExceptionGroup("", (ValueError(),)) + # match + with RaisesGroup( + ValueError, + ValueError, + RaisesExc(TypeError, match="^expected int$"), + match="^my group$", + ): + raise ExceptionGroup( + "my group", + [ + ValueError(), + TypeError("expected int"), + ValueError(), + ], + ) + # check + with RaisesGroup( + KeyboardInterrupt, + match="^hello$", + check=lambda x: isinstance(x.__cause__, ValueError), + ): + raise BaseExceptionGroup("hello", [KeyboardInterrupt()]) from ValueError + # nested groups + with RaisesGroup(RaisesGroup(ValueError)): + raise ExceptionGroup("", (ExceptionGroup("", (ValueError(),)),)) + + # flatten_subgroups + with RaisesGroup(ValueError, flatten_subgroups=True): + raise ExceptionGroup("", (ExceptionGroup("", (ValueError(),)),)) + + # allow_unwrapped + with RaisesGroup(ValueError, allow_unwrapped=True): + raise ValueError + + + :meth:`RaisesGroup.matches` can also be used directly to check a standalone exception group. + + + The matching algorithm is greedy, which means cases such as this may fail:: + + with RaisesGroup(ValueError, RaisesExc(ValueError, match="hello")): + raise ExceptionGroup("", (ValueError("hello"), ValueError("goodbye"))) + + even though it generally does not care about the order of the exceptions in the group. + To avoid the above you should specify the first :exc:`ValueError` with a :class:`RaisesExc` as well. + + .. note:: + When raised exceptions don't match the expected ones, you'll get a detailed error + message explaining why. This includes ``repr(check)`` if set, which in Python can be + overly verbose, showing memory locations etc etc. + + If installed and imported (in e.g. ``conftest.py``), the ``hypothesis`` library will + monkeypatch this output to provide shorter & more readable repr's. + """ + + # allow_unwrapped=True requires: singular exception, exception not being + # RaisesGroup instance, match is None, check is None + @overload + def __init__( + self, + expected_exception: type[BaseExcT_co] | RaisesExc[BaseExcT_co], + /, + *, + allow_unwrapped: Literal[True], + flatten_subgroups: bool = False, + ) -> None: ... + + # flatten_subgroups = True also requires no nested RaisesGroup + @overload + def __init__( + self, + expected_exception: type[BaseExcT_co] | RaisesExc[BaseExcT_co], + /, + *other_exceptions: type[BaseExcT_co] | RaisesExc[BaseExcT_co], + flatten_subgroups: Literal[True], + match: str | Pattern[str] | None = None, + check: Callable[[BaseExceptionGroup[BaseExcT_co]], bool] | None = None, + ) -> None: ... + + # simplify the typevars if possible (the following 3 are equivalent but go simpler->complicated) + # ... the first handles RaisesGroup[ValueError], the second RaisesGroup[ExceptionGroup[ValueError]], + # the third RaisesGroup[ValueError | ExceptionGroup[ValueError]]. + # ... otherwise, we will get results like RaisesGroup[ValueError | ExceptionGroup[Never]] (I think) + # (technically correct but misleading) + @overload + def __init__( + self: RaisesGroup[ExcT_1], + expected_exception: type[ExcT_1] | RaisesExc[ExcT_1], + /, + *other_exceptions: type[ExcT_1] | RaisesExc[ExcT_1], + match: str | Pattern[str] | None = None, + check: Callable[[ExceptionGroup[ExcT_1]], bool] | None = None, + ) -> None: ... + + @overload + def __init__( + self: RaisesGroup[ExceptionGroup[ExcT_2]], + expected_exception: RaisesGroup[ExcT_2], + /, + *other_exceptions: RaisesGroup[ExcT_2], + match: str | Pattern[str] | None = None, + check: Callable[[ExceptionGroup[ExceptionGroup[ExcT_2]]], bool] | None = None, + ) -> None: ... + + @overload + def __init__( + self: RaisesGroup[ExcT_1 | ExceptionGroup[ExcT_2]], + expected_exception: type[ExcT_1] | RaisesExc[ExcT_1] | RaisesGroup[ExcT_2], + /, + *other_exceptions: type[ExcT_1] | RaisesExc[ExcT_1] | RaisesGroup[ExcT_2], + match: str | Pattern[str] | None = None, + check: ( + Callable[[ExceptionGroup[ExcT_1 | ExceptionGroup[ExcT_2]]], bool] | None + ) = None, + ) -> None: ... + + # same as the above 3 but handling BaseException + @overload + def __init__( + self: RaisesGroup[BaseExcT_1], + expected_exception: type[BaseExcT_1] | RaisesExc[BaseExcT_1], + /, + *other_exceptions: type[BaseExcT_1] | RaisesExc[BaseExcT_1], + match: str | Pattern[str] | None = None, + check: Callable[[BaseExceptionGroup[BaseExcT_1]], bool] | None = None, + ) -> None: ... + + @overload + def __init__( + self: RaisesGroup[BaseExceptionGroup[BaseExcT_2]], + expected_exception: RaisesGroup[BaseExcT_2], + /, + *other_exceptions: RaisesGroup[BaseExcT_2], + match: str | Pattern[str] | None = None, + check: ( + Callable[[BaseExceptionGroup[BaseExceptionGroup[BaseExcT_2]]], bool] | None + ) = None, + ) -> None: ... + + @overload + def __init__( + self: RaisesGroup[BaseExcT_1 | BaseExceptionGroup[BaseExcT_2]], + expected_exception: type[BaseExcT_1] + | RaisesExc[BaseExcT_1] + | RaisesGroup[BaseExcT_2], + /, + *other_exceptions: type[BaseExcT_1] + | RaisesExc[BaseExcT_1] + | RaisesGroup[BaseExcT_2], + match: str | Pattern[str] | None = None, + check: ( + Callable[ + [BaseExceptionGroup[BaseExcT_1 | BaseExceptionGroup[BaseExcT_2]]], + bool, + ] + | None + ) = None, + ) -> None: ... + + def __init__( + self: RaisesGroup[ExcT_1 | BaseExcT_1 | BaseExceptionGroup[BaseExcT_2]], + expected_exception: type[BaseExcT_1] + | RaisesExc[BaseExcT_1] + | RaisesGroup[BaseExcT_2], + /, + *other_exceptions: type[BaseExcT_1] + | RaisesExc[BaseExcT_1] + | RaisesGroup[BaseExcT_2], + allow_unwrapped: bool = False, + flatten_subgroups: bool = False, + match: str | Pattern[str] | None = None, + check: ( + Callable[[BaseExceptionGroup[BaseExcT_1]], bool] + | Callable[[ExceptionGroup[ExcT_1]], bool] + | None + ) = None, + ): + # The type hint on the `self` and `check` parameters uses different formats + # that are *very* hard to reconcile while adhering to the overloads, so we cast + # it to avoid an error when passing it to super().__init__ + check = cast( + "Callable[[BaseExceptionGroup[ExcT_1|BaseExcT_1|BaseExceptionGroup[BaseExcT_2]]], bool]", + check, + ) + super().__init__(match=match, check=check) + self.allow_unwrapped = allow_unwrapped + self.flatten_subgroups: bool = flatten_subgroups + self.is_baseexception = False + + if allow_unwrapped and other_exceptions: + raise ValueError( + "You cannot specify multiple exceptions with `allow_unwrapped=True.`" + " If you want to match one of multiple possible exceptions you should" + " use a `RaisesExc`." + " E.g. `RaisesExc(check=lambda e: isinstance(e, (...)))`", + ) + if allow_unwrapped and isinstance(expected_exception, RaisesGroup): + raise ValueError( + "`allow_unwrapped=True` has no effect when expecting a `RaisesGroup`." + " You might want it in the expected `RaisesGroup`, or" + " `flatten_subgroups=True` if you don't care about the structure.", + ) + if allow_unwrapped and (match is not None or check is not None): + raise ValueError( + "`allow_unwrapped=True` bypasses the `match` and `check` parameters" + " if the exception is unwrapped. If you intended to match/check the" + " exception you should use a `RaisesExc` object. If you want to match/check" + " the exceptiongroup when the exception *is* wrapped you need to" + " do e.g. `if isinstance(exc.value, ExceptionGroup):" + " assert RaisesGroup(...).matches(exc.value)` afterwards.", + ) + + self.expected_exceptions: tuple[ + type[BaseExcT_co] | RaisesExc[BaseExcT_co] | RaisesGroup[BaseException], ... + ] = tuple( + self._parse_excgroup(e, "a BaseException type, RaisesExc, or RaisesGroup") + for e in ( + expected_exception, + *other_exceptions, + ) + ) + + def _parse_excgroup( + self, + exc: ( + type[BaseExcT_co] + | types.GenericAlias + | RaisesExc[BaseExcT_1] + | RaisesGroup[BaseExcT_2] + ), + expected: str, + ) -> type[BaseExcT_co] | RaisesExc[BaseExcT_1] | RaisesGroup[BaseExcT_2]: + # verify exception type and set `self.is_baseexception` + if isinstance(exc, RaisesGroup): + if self.flatten_subgroups: + raise ValueError( + "You cannot specify a nested structure inside a RaisesGroup with" + " `flatten_subgroups=True`. The parameter will flatten subgroups" + " in the raised exceptiongroup before matching, which would never" + " match a nested structure.", + ) + self.is_baseexception |= exc.is_baseexception + exc._nested = True + return exc + elif isinstance(exc, RaisesExc): + self.is_baseexception |= exc.is_baseexception + exc._nested = True + return exc + elif isinstance(exc, tuple): + raise TypeError( + f"Expected {expected}, but got {type(exc).__name__!r}.\n" + "RaisesGroup does not support tuples of exception types when expecting one of " + "several possible exception types like RaisesExc.\n" + "If you meant to expect a group with multiple exceptions, list them as separate arguments." + ) + else: + return super()._parse_exc(exc, expected) + + @overload + def __enter__( + self: RaisesGroup[ExcT_1], + ) -> ExceptionInfo[ExceptionGroup[ExcT_1]]: ... + @overload + def __enter__( + self: RaisesGroup[BaseExcT_1], + ) -> ExceptionInfo[BaseExceptionGroup[BaseExcT_1]]: ... + + def __enter__(self) -> ExceptionInfo[BaseExceptionGroup[BaseException]]: + self.excinfo: ExceptionInfo[BaseExceptionGroup[BaseExcT_co]] = ( + ExceptionInfo.for_later() + ) + return self.excinfo + + def __repr__(self) -> str: + reqs = [ + e.__name__ if isinstance(e, type) else repr(e) + for e in self.expected_exceptions + ] + if self.allow_unwrapped: + reqs.append(f"allow_unwrapped={self.allow_unwrapped}") + if self.flatten_subgroups: + reqs.append(f"flatten_subgroups={self.flatten_subgroups}") + if self.match is not None: + # If no flags were specified, discard the redundant re.compile() here. + reqs.append(f"match={_match_pattern(self.match)!r}") + if self.check is not None: + reqs.append(f"check={repr_callable(self.check)}") + return f"RaisesGroup({', '.join(reqs)})" + + def _unroll_exceptions( + self, + exceptions: Sequence[BaseException], + ) -> Sequence[BaseException]: + """Used if `flatten_subgroups=True`.""" + res: list[BaseException] = [] + for exc in exceptions: + if isinstance(exc, BaseExceptionGroup): + res.extend(self._unroll_exceptions(exc.exceptions)) + + else: + res.append(exc) + return res + + @overload + def matches( + self: RaisesGroup[ExcT_1], + exception: BaseException | None, + ) -> TypeGuard[ExceptionGroup[ExcT_1]]: ... + @overload + def matches( + self: RaisesGroup[BaseExcT_1], + exception: BaseException | None, + ) -> TypeGuard[BaseExceptionGroup[BaseExcT_1]]: ... + + def matches( + self, + exception: BaseException | None, + ) -> bool: + """Check if an exception matches the requirements of this RaisesGroup. + If it fails, `RaisesGroup.fail_reason` will be set. + + Example:: + + with pytest.raises(TypeError) as excinfo: + ... + assert RaisesGroup(ValueError).matches(excinfo.value.__cause__) + # the above line is equivalent to + myexc = excinfo.value.__cause + assert isinstance(myexc, BaseExceptionGroup) + assert len(myexc.exceptions) == 1 + assert isinstance(myexc.exceptions[0], ValueError) + """ + self._fail_reason = None + if exception is None: + self._fail_reason = "exception is None" + return False + if not isinstance(exception, BaseExceptionGroup): + # we opt to only print type of the exception here, as the repr would + # likely be quite long + not_group_msg = f"`{type(exception).__name__}()` is not an exception group" + if len(self.expected_exceptions) > 1: + self._fail_reason = not_group_msg + return False + # if we have 1 expected exception, check if it would work even if + # allow_unwrapped is not set + res = self._check_expected(self.expected_exceptions[0], exception) + if res is None and self.allow_unwrapped: + return True + + if res is None: + self._fail_reason = ( + f"{not_group_msg}, but would match with `allow_unwrapped=True`" + ) + elif self.allow_unwrapped: + self._fail_reason = res + else: + self._fail_reason = not_group_msg + return False + + actual_exceptions: Sequence[BaseException] = exception.exceptions + if self.flatten_subgroups: + actual_exceptions = self._unroll_exceptions(actual_exceptions) + + if not self._check_match(exception): + self._fail_reason = cast(str, self._fail_reason) + old_reason = self._fail_reason + if ( + len(actual_exceptions) == len(self.expected_exceptions) == 1 + and isinstance(expected := self.expected_exceptions[0], type) + and isinstance(actual := actual_exceptions[0], expected) + and self._check_match(actual) + ): + assert self.match is not None, "can't be None if _check_match failed" + assert self._fail_reason is old_reason is not None + self._fail_reason += ( + f"\n" + f" but matched the expected `{self._repr_expected(expected)}`.\n" + f" You might want " + f"`RaisesGroup(RaisesExc({expected.__name__}, match={_match_pattern(self.match)!r}))`" + ) + else: + self._fail_reason = old_reason + return False + + # do the full check on expected exceptions + if not self._check_exceptions( + exception, + actual_exceptions, + ): + self._fail_reason = cast(str, self._fail_reason) + assert self._fail_reason is not None + old_reason = self._fail_reason + # if we're not expecting a nested structure, and there is one, do a second + # pass where we try flattening it + if ( + not self.flatten_subgroups + and not any( + isinstance(e, RaisesGroup) for e in self.expected_exceptions + ) + and any(isinstance(e, BaseExceptionGroup) for e in actual_exceptions) + and self._check_exceptions( + exception, + self._unroll_exceptions(exception.exceptions), + ) + ): + # only indent if it's a single-line reason. In a multi-line there's already + # indented lines that this does not belong to. + indent = " " if "\n" not in self._fail_reason else "" + self._fail_reason = ( + old_reason + + f"\n{indent}Did you mean to use `flatten_subgroups=True`?" + ) + else: + self._fail_reason = old_reason + return False + + # Only run `self.check` once we know `exception` is of the correct type. + if not self._check_check(exception): + reason = ( + cast(str, self._fail_reason) + f" on the {type(exception).__name__}" + ) + if ( + len(actual_exceptions) == len(self.expected_exceptions) == 1 + and isinstance(expected := self.expected_exceptions[0], type) + # we explicitly break typing here :) + and self._check_check(actual_exceptions[0]) # type: ignore[arg-type] + ): + self._fail_reason = reason + ( + f", but did return True for the expected {self._repr_expected(expected)}." + f" You might want RaisesGroup(RaisesExc({expected.__name__}, check=<...>))" + ) + else: + self._fail_reason = reason + return False + + return True + + @staticmethod + def _check_expected( + expected_type: ( + type[BaseException] | RaisesExc[BaseException] | RaisesGroup[BaseException] + ), + exception: BaseException, + ) -> str | None: + """Helper method for `RaisesGroup.matches` and `RaisesGroup._check_exceptions` + to check one of potentially several expected exceptions.""" + if isinstance(expected_type, type): + return _check_raw_type(expected_type, exception) + res = expected_type.matches(exception) + if res: + return None + assert expected_type.fail_reason is not None + if expected_type.fail_reason.startswith("\n"): + return f"\n{expected_type!r}: {indent(expected_type.fail_reason, ' ')}" + return f"{expected_type!r}: {expected_type.fail_reason}" + + @staticmethod + def _repr_expected(e: type[BaseException] | AbstractRaises[BaseException]) -> str: + """Get the repr of an expected type/RaisesExc/RaisesGroup, but we only want + the name if it's a type""" + if isinstance(e, type): + return _exception_type_name(e) + return repr(e) + + @overload + def _check_exceptions( + self: RaisesGroup[ExcT_1], + _exception: Exception, + actual_exceptions: Sequence[Exception], + ) -> TypeGuard[ExceptionGroup[ExcT_1]]: ... + @overload + def _check_exceptions( + self: RaisesGroup[BaseExcT_1], + _exception: BaseException, + actual_exceptions: Sequence[BaseException], + ) -> TypeGuard[BaseExceptionGroup[BaseExcT_1]]: ... + + def _check_exceptions( + self, + _exception: BaseException, + actual_exceptions: Sequence[BaseException], + ) -> bool: + """Helper method for RaisesGroup.matches that attempts to pair up expected and actual exceptions""" + # The _exception parameter is not used, but necessary for the TypeGuard + + # full table with all results + results = ResultHolder(self.expected_exceptions, actual_exceptions) + + # (indexes of) raised exceptions that haven't (yet) found an expected + remaining_actual = list(range(len(actual_exceptions))) + # (indexes of) expected exceptions that haven't found a matching raised + failed_expected: list[int] = [] + # successful greedy matches + matches: dict[int, int] = {} + + # loop over expected exceptions first to get a more predictable result + for i_exp, expected in enumerate(self.expected_exceptions): + for i_rem in remaining_actual: + res = self._check_expected(expected, actual_exceptions[i_rem]) + results.set_result(i_exp, i_rem, res) + if res is None: + remaining_actual.remove(i_rem) + matches[i_exp] = i_rem + break + else: + failed_expected.append(i_exp) + + # All exceptions matched up successfully + if not remaining_actual and not failed_expected: + return True + + # in case of a single expected and single raised we simplify the output + if 1 == len(actual_exceptions) == len(self.expected_exceptions): + assert not matches + self._fail_reason = res + return False + + # The test case is failing, so we can do a slow and exhaustive check to find + # duplicate matches etc that will be helpful in debugging + for i_exp, expected in enumerate(self.expected_exceptions): + for i_actual, actual in enumerate(actual_exceptions): + if results.has_result(i_exp, i_actual): + continue + results.set_result( + i_exp, i_actual, self._check_expected(expected, actual) + ) + + successful_str = ( + f"{len(matches)} matched exception{'s' if len(matches) > 1 else ''}. " + if matches + else "" + ) + + # all expected were found + if not failed_expected and results.no_match_for_actual(remaining_actual): + self._fail_reason = ( + f"{successful_str}Unexpected exception(s):" + f" {[actual_exceptions[i] for i in remaining_actual]!r}" + ) + return False + # all raised exceptions were expected + if not remaining_actual and results.no_match_for_expected(failed_expected): + no_match_for_str = ", ".join( + self._repr_expected(self.expected_exceptions[i]) + for i in failed_expected + ) + self._fail_reason = f"{successful_str}Too few exceptions raised, found no match for: [{no_match_for_str}]" + return False + + # if there's only one remaining and one failed, and the unmatched didn't match anything else, + # we elect to only print why the remaining and the failed didn't match. + if ( + 1 == len(remaining_actual) == len(failed_expected) + and results.no_match_for_actual(remaining_actual) + and results.no_match_for_expected(failed_expected) + ): + self._fail_reason = f"{successful_str}{results.get_result(failed_expected[0], remaining_actual[0])}" + return False + + # there's both expected and raised exceptions without matches + s = "" + if matches: + s += f"\n{successful_str}" + indent_1 = " " * 2 + indent_2 = " " * 4 + + if not remaining_actual: + s += "\nToo few exceptions raised!" + elif not failed_expected: + s += "\nUnexpected exception(s)!" + + if failed_expected: + s += "\nThe following expected exceptions did not find a match:" + rev_matches = {v: k for k, v in matches.items()} + for i_failed in failed_expected: + s += ( + f"\n{indent_1}{self._repr_expected(self.expected_exceptions[i_failed])}" + ) + for i_actual, actual in enumerate(actual_exceptions): + if results.get_result(i_exp, i_actual) is None: + # we print full repr of match target + s += ( + f"\n{indent_2}It matches {backquote(repr(actual))} which was paired with " + + backquote( + self._repr_expected( + self.expected_exceptions[rev_matches[i_actual]] + ) + ) + ) + + if remaining_actual: + s += "\nThe following raised exceptions did not find a match" + for i_actual in remaining_actual: + s += f"\n{indent_1}{actual_exceptions[i_actual]!r}:" + for i_exp, expected in enumerate(self.expected_exceptions): + res = results.get_result(i_exp, i_actual) + if i_exp in failed_expected: + assert res is not None + if res[0] != "\n": + s += "\n" + s += indent(res, indent_2) + if res is None: + # we print full repr of match target + s += ( + f"\n{indent_2}It matches {backquote(self._repr_expected(expected))} " + f"which was paired with {backquote(repr(actual_exceptions[matches[i_exp]]))}" + ) + + if len(self.expected_exceptions) == len(actual_exceptions) and possible_match( + results + ): + s += ( + "\nThere exist a possible match when attempting an exhaustive check," + " but RaisesGroup uses a greedy algorithm. " + "Please make your expected exceptions more stringent with `RaisesExc` etc" + " so the greedy algorithm can function." + ) + self._fail_reason = s + return False + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: types.TracebackType | None, + ) -> bool: + __tracebackhide__ = True + if exc_type is None: + fail(f"DID NOT RAISE any exception, expected `{self.expected_type()}`") + + assert self.excinfo is not None, ( + "Internal error - should have been constructed in __enter__" + ) + + # group_str is the only thing that differs between RaisesExc and RaisesGroup... + # I might just scrap it? Or make it part of fail_reason + group_str = ( + "(group)" + if self.allow_unwrapped and not issubclass(exc_type, BaseExceptionGroup) + else "group" + ) + + if not self.matches(exc_val): + fail(f"Raised exception {group_str} did not match: {self._fail_reason}") + + # Cast to narrow the exception type now that it's verified.... + # even though the TypeGuard in self.matches should be narrowing + exc_info = cast( + "tuple[type[BaseExceptionGroup[BaseExcT_co]], BaseExceptionGroup[BaseExcT_co], types.TracebackType]", + (exc_type, exc_val, exc_tb), + ) + self.excinfo.fill_unfilled(exc_info) + return True + + def expected_type(self) -> str: + subexcs = [] + for e in self.expected_exceptions: + if isinstance(e, RaisesExc): + subexcs.append(repr(e)) + elif isinstance(e, RaisesGroup): + subexcs.append(e.expected_type()) + elif isinstance(e, type): + subexcs.append(e.__name__) + else: # pragma: no cover + raise AssertionError("unknown type") + group_type = "Base" if self.is_baseexception else "" + return f"{group_type}ExceptionGroup({', '.join(subexcs)})" + + +@final +class NotChecked: + """Singleton for unchecked values in ResultHolder""" + + +class ResultHolder: + """Container for results of checking exceptions. + Used in RaisesGroup._check_exceptions and possible_match. + """ + + def __init__( + self, + expected_exceptions: tuple[ + type[BaseException] | AbstractRaises[BaseException], ... + ], + actual_exceptions: Sequence[BaseException], + ) -> None: + self.results: list[list[str | type[NotChecked] | None]] = [ + [NotChecked for _ in expected_exceptions] for _ in actual_exceptions + ] + + def set_result(self, expected: int, actual: int, result: str | None) -> None: + self.results[actual][expected] = result + + def get_result(self, expected: int, actual: int) -> str | None: + res = self.results[actual][expected] + assert res is not NotChecked + # mypy doesn't support identity checking against anything but None + return res # type: ignore[return-value] + + def has_result(self, expected: int, actual: int) -> bool: + return self.results[actual][expected] is not NotChecked + + def no_match_for_expected(self, expected: list[int]) -> bool: + for i in expected: + for actual_results in self.results: + assert actual_results[i] is not NotChecked + if actual_results[i] is None: + return False + return True + + def no_match_for_actual(self, actual: list[int]) -> bool: + for i in actual: + for res in self.results[i]: + assert res is not NotChecked + if res is None: + return False + return True + + +def possible_match(results: ResultHolder, used: set[int] | None = None) -> bool: + if used is None: + used = set() + curr_row = len(used) + if curr_row == len(results.results): + return True + return any( + val is None and i not in used and possible_match(results, used | {i}) + for (i, val) in enumerate(results.results[curr_row]) + ) diff --git a/venv/lib/python3.10/site-packages/_pytest/recwarn.py b/venv/lib/python3.10/site-packages/_pytest/recwarn.py index d76ea02..e3db717 100644 --- a/venv/lib/python3.10/site-packages/_pytest/recwarn.py +++ b/venv/lib/python3.10/site-packages/_pytest/recwarn.py @@ -1,25 +1,29 @@ +# mypy: allow-untyped-defs """Record warnings during test function execution.""" -import re -import warnings + +from __future__ import annotations + +from collections.abc import Callable +from collections.abc import Generator +from collections.abc import Iterator from pprint import pformat +import re from types import TracebackType from typing import Any -from typing import Callable -from typing import Generator -from typing import Iterator -from typing import List -from typing import Optional -from typing import Pattern -from typing import Tuple -from typing import Type +from typing import final +from typing import overload +from typing import TYPE_CHECKING from typing import TypeVar -from typing import Union -from _pytest.compat import final -from _pytest.compat import overload + +if TYPE_CHECKING: + from typing_extensions import Self + +import warnings + from _pytest.deprecated import check_ispytest -from _pytest.deprecated import WARNS_NONE_ARG from _pytest.fixtures import fixture +from _pytest.outcomes import Exit from _pytest.outcomes import fail @@ -27,11 +31,10 @@ T = TypeVar("T") @fixture -def recwarn() -> Generator["WarningsRecorder", None, None]: +def recwarn() -> Generator[WarningsRecorder]: """Return a :class:`WarningsRecorder` instance that records all warnings emitted by test functions. - See https://docs.pytest.org/en/latest/how-to/capture-warnings.html for information - on warning categories. + See :ref:`warnings` for information on warning categories. """ wrec = WarningsRecorder(_ispytest=True) with wrec: @@ -41,22 +44,18 @@ def recwarn() -> Generator["WarningsRecorder", None, None]: @overload def deprecated_call( - *, match: Optional[Union[str, Pattern[str]]] = ... -) -> "WarningsRecorder": - ... + *, match: str | re.Pattern[str] | None = ... +) -> WarningsRecorder: ... @overload -def deprecated_call( # noqa: F811 - func: Callable[..., T], *args: Any, **kwargs: Any -) -> T: - ... +def deprecated_call(func: Callable[..., T], *args: Any, **kwargs: Any) -> T: ... -def deprecated_call( # noqa: F811 - func: Optional[Callable[..., Any]] = None, *args: Any, **kwargs: Any -) -> Union["WarningsRecorder", Any]: - """Assert that code produces a ``DeprecationWarning`` or ``PendingDeprecationWarning``. +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:: @@ -81,46 +80,46 @@ def deprecated_call( # noqa: F811 """ __tracebackhide__ = True if func is not None: - args = (func,) + args - return warns((DeprecationWarning, PendingDeprecationWarning), *args, **kwargs) + args = (func, *args) + return warns( + (DeprecationWarning, PendingDeprecationWarning, FutureWarning), *args, **kwargs + ) @overload def warns( - expected_warning: Union[Type[Warning], Tuple[Type[Warning], ...]] = ..., + expected_warning: type[Warning] | tuple[type[Warning], ...] = ..., *, - match: Optional[Union[str, Pattern[str]]] = ..., -) -> "WarningsChecker": - ... + match: str | re.Pattern[str] | None = ..., +) -> WarningsChecker: ... @overload -def warns( # noqa: F811 - expected_warning: Union[Type[Warning], Tuple[Type[Warning], ...]], +def warns( + expected_warning: type[Warning] | tuple[type[Warning], ...], func: Callable[..., T], *args: Any, **kwargs: Any, -) -> T: - ... +) -> T: ... -def warns( # noqa: F811 - expected_warning: Union[Type[Warning], Tuple[Type[Warning], ...]] = Warning, +def warns( + expected_warning: type[Warning] | tuple[type[Warning], ...] = Warning, *args: Any, - match: Optional[Union[str, Pattern[str]]] = None, + match: str | re.Pattern[str] | None = None, **kwargs: Any, -) -> Union["WarningsChecker", Any]: +) -> WarningsChecker | Any: r"""Assert that code raises a particular class of warning. - Specifically, the parameter ``expected_warning`` can be a warning class or sequence + 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 raised (regardless of whether it is an ``expected_warning`` or not). + 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, which will capture all the raised - warnings inside it:: + This function can be used as a context manager:: >>> import pytest >>> with pytest.warns(RuntimeWarning): @@ -135,8 +134,9 @@ def warns( # noqa: F811 >>> with pytest.warns(UserWarning, match=r'must be \d+$'): ... warnings.warn("value must be 42", UserWarning) - >>> with pytest.warns(UserWarning, match=r'must be \d+$'): - ... warnings.warn("this is not here", 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... @@ -167,7 +167,7 @@ def warns( # noqa: F811 return func(*args[1:], **kwargs) -class WarningsRecorder(warnings.catch_warnings): # type:ignore[type-arg] +class WarningsRecorder(warnings.catch_warnings): """A context manager to record raised warnings. Each recorded warning is an instance of :class:`warnings.WarningMessage`. @@ -182,21 +182,20 @@ class WarningsRecorder(warnings.catch_warnings): # type:ignore[type-arg] def __init__(self, *, _ispytest: bool = False) -> None: check_ispytest(_ispytest) - # Type ignored due to the way typeshed handles warnings.catch_warnings. - super().__init__(record=True) # type: ignore[call-arg] + super().__init__(record=True) self._entered = False - self._list: List[warnings.WarningMessage] = [] + self._list: list[warnings.WarningMessage] = [] @property - def list(self) -> List["warnings.WarningMessage"]: + def list(self) -> list[warnings.WarningMessage]: """The list of recorded warnings.""" return self._list - def __getitem__(self, i: int) -> "warnings.WarningMessage": + def __getitem__(self, i: int) -> warnings.WarningMessage: """Get a recorded warning by index.""" return self._list[i] - def __iter__(self) -> Iterator["warnings.WarningMessage"]: + def __iter__(self) -> Iterator[warnings.WarningMessage]: """Iterate through the recorded warnings.""" return iter(self._list) @@ -204,11 +203,22 @@ class WarningsRecorder(warnings.catch_warnings): # type:ignore[type-arg] """The number of recorded warnings.""" return len(self._list) - def pop(self, cls: Type[Warning] = Warning) -> "warnings.WarningMessage": - """Pop the first recorded warning, raise exception if not exists.""" + 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 issubclass(w.category, cls): - return self._list.pop(i) + 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") @@ -216,9 +226,9 @@ class WarningsRecorder(warnings.catch_warnings): # type:ignore[type-arg] """Clear the list of recorded warnings.""" self._list[:] = [] - # Type ignored because it doesn't exactly warnings.catch_warnings.__enter__ - # -- it returns a List but we only emulate one. - def __enter__(self) -> "WarningsRecorder": # type: ignore + # Type ignored because we basically want the `catch_warnings` generic type + # parameter to be ourselves but that is not possible(?). + def __enter__(self) -> Self: # type: ignore[override] if self._entered: __tracebackhide__ = True raise RuntimeError(f"Cannot enter {self!r} twice") @@ -231,9 +241,9 @@ class WarningsRecorder(warnings.catch_warnings): # type:ignore[type-arg] def __exit__( self, - exc_type: Optional[Type[BaseException]], - exc_val: Optional[BaseException], - exc_tb: Optional[TracebackType], + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, ) -> None: if not self._entered: __tracebackhide__ = True @@ -250,10 +260,8 @@ class WarningsRecorder(warnings.catch_warnings): # type:ignore[type-arg] class WarningsChecker(WarningsRecorder): def __init__( self, - expected_warning: Optional[ - Union[Type[Warning], Tuple[Type[Warning], ...]] - ] = Warning, - match_expr: Optional[Union[str, Pattern[str]]] = None, + expected_warning: type[Warning] | tuple[type[Warning], ...] = Warning, + match_expr: str | re.Pattern[str] | None = None, *, _ispytest: bool = False, ) -> None: @@ -261,15 +269,14 @@ class WarningsChecker(WarningsRecorder): super().__init__(_ispytest=True) msg = "exceptions must be derived from Warning, not %s" - if expected_warning is None: - warnings.warn(WARNS_NONE_ARG, stacklevel=4) - expected_warning_tup = None - elif isinstance(expected_warning, tuple): + 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 issubclass(expected_warning, Warning): + elif isinstance(expected_warning, type) and issubclass( + expected_warning, Warning + ): expected_warning_tup = (expected_warning,) else: raise TypeError(msg % type(expected_warning)) @@ -277,37 +284,84 @@ class WarningsChecker(WarningsRecorder): 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: Optional[Type[BaseException]], - exc_val: Optional[BaseException], - exc_tb: Optional[TracebackType], + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, ) -> None: super().__exit__(exc_type, exc_val, exc_tb) __tracebackhide__ = True - def found_str(): + # 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) - # only check if we're not currently handling an exception - if exc_type is None and exc_val is None and exc_tb is None: - if self.expected_warning is not None: - if not any(issubclass(r.category, self.expected_warning) for r in self): - __tracebackhide__ = True - fail( - f"DID NOT WARN. No warnings of type {self.expected_warning} were emitted.\n" - f"The list of emitted warnings is: {found_str()}." + 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, ) - elif self.match_expr is not None: - for r in self: - if issubclass(r.category, self.expected_warning): - if re.compile(self.match_expr).search(str(r.message)): - break - else: - fail( - f"""\ -DID NOT WARN. No warnings of type {self.expected_warning} matching the regex were emitted. - Regex: {self.match_expr} - Emitted warnings: {found_str()}""" - ) + + # 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/python3.10/site-packages/_pytest/reports.py b/venv/lib/python3.10/site-packages/_pytest/reports.py index 74e8794..011a69d 100644 --- a/venv/lib/python3.10/site-packages/_pytest/reports.py +++ b/venv/lib/python3.10/site-packages/_pytest/reports.py @@ -1,21 +1,21 @@ +# mypy: allow-untyped-defs +from __future__ import annotations + +from collections.abc import Iterable +from collections.abc import Iterator +from collections.abc import Mapping +from collections.abc import Sequence import dataclasses -import os from io import StringIO +import os from pprint import pprint +import sys from typing import Any from typing import cast -from typing import Dict -from typing import Iterable -from typing import Iterator -from typing import List -from typing import Mapping +from typing import final +from typing import Literal from typing import NoReturn -from typing import Optional -from typing import Tuple -from typing import Type from typing import TYPE_CHECKING -from typing import TypeVar -from typing import Union from _pytest._code.code import ExceptionChainRepr from _pytest._code.code import ExceptionInfo @@ -29,14 +29,19 @@ 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.compat import final 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 sys.version_info < (3, 11): + from exceptiongroup import BaseExceptionGroup + + if TYPE_CHECKING: - from typing_extensions import Literal + from typing_extensions import Self from _pytest.runner import CallInfo @@ -46,33 +51,29 @@ def getworkerinfoline(node): return node._workerinfocache except AttributeError: d = node.workerinfo - ver = "%s.%s.%s" % d["version_info"][:3] + ver = "{}.{}.{}".format(*d["version_info"][:3]) node._workerinfocache = s = "[{}] {} -- Python {} {}".format( d["id"], d["sysplatform"], ver, d["executable"] ) return s -_R = TypeVar("_R", bound="BaseReport") - - class BaseReport: - when: Optional[str] - location: Optional[Tuple[str, Optional[int], str]] - longrepr: Union[ - None, ExceptionInfo[BaseException], Tuple[str, int, str], str, TerminalRepr - ] - sections: List[Tuple[str, str]] + 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']" + 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 __getattr__(self, key: str) -> Any: ... def toterminal(self, out: TerminalWriter) -> None: if hasattr(self, "node"): @@ -94,7 +95,7 @@ class BaseReport: s = "" out.line(s) - def get_sections(self, prefix: str) -> Iterator[Tuple[str, str]]: + def get_sections(self, prefix: str) -> Iterator[tuple[str, str]]: for name, content in self.sections: if name.startswith(prefix): yield prefix, content @@ -176,7 +177,7 @@ class BaseReport: return True @property - def head_line(self) -> Optional[str]: + def head_line(self) -> str | None: """**Experimental** The head line shown with longrepr output for this report, more commonly during traceback representation during failures:: @@ -192,17 +193,32 @@ class BaseReport: even in patch releases. """ if self.location is not None: - fspath, lineno, domain = self.location + _fspath, _lineno, domain = self.location return domain return None - def _get_verbose_word(self, config: Config): + 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 ) - return verbose - def _to_json(self) -> Dict[str, Any]: + 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. @@ -213,7 +229,7 @@ class BaseReport: return _report_to_json(self) @classmethod - def _from_json(cls: Type[_R], reportdict: Dict[str, object]) -> _R: + 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. @@ -227,20 +243,65 @@ class BaseReport: def _report_unserialization_failure( - type_name: str, report_class: Type[BaseReport], reportdict + type_name: str, report_class: type[BaseReport], reportdict ) -> NoReturn: url = "https://github.com/pytest-dev/pytest/issues" stream = StringIO() pprint("-" * 100, stream=stream) - pprint("INTERNALERROR: Unknown entry type returned: %s" % type_name, stream=stream) - pprint("report_name: %s" % report_class, 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("Please report this bug at %s" % url, stream=stream) + pprint(f"Please report this bug at {url}", stream=stream) pprint("-" * 100, stream=stream) raise RuntimeError(stream.getvalue()) -@final +def _format_failed_longrepr( + item: Item, call: CallInfo[None], excinfo: ExceptionInfo[BaseException] +): + 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") + ) + return longrepr + + +def _format_exception_group_all_skipped_longrepr( + item: Item, + excinfo: ExceptionInfo[BaseExceptionGroup[BaseException | BaseExceptionGroup]], +) -> tuple[str, int, str]: + r = excinfo._getreprcrash() + assert r is not None, ( + "There should always be a traceback entry for skipping a test." + ) + if all( + getattr(skip, "_use_item_location", False) for skip in excinfo.value.exceptions + ): + path, line = item.reportinfo()[:2] + assert line is not None + loc = (os.fspath(path), line + 1) + default_msg = "skipped" + else: + loc = (str(r.path), r.lineno) + default_msg = r.message + + # Get all unique skip messages. + msgs: list[str] = [] + for exception in excinfo.value.exceptions: + m = getattr(exception, "msg", None) or ( + exception.args[0] if exception.args else None + ) + if m and m not in msgs: + msgs.append(m) + + reason = "; ".join(msgs) if msgs else default_msg + longrepr = (*loc, reason) + return longrepr + + class TestReport(BaseReport): """Basic test report object (also used for setup and teardown calls if they fail). @@ -250,21 +311,27 @@ class TestReport(BaseReport): __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, Optional[int], str], + location: tuple[str, int | None, str], keywords: Mapping[str, Any], - outcome: "Literal['passed', 'failed', 'skipped']", - longrepr: Union[ - None, ExceptionInfo[BaseException], Tuple[str, int, str], str, TerminalRepr - ], - when: "Literal['setup', 'call', 'teardown']", - sections: Iterable[Tuple[str, str]] = (), + 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: Optional[Iterable[Tuple[str, object]]] = None, + user_properties: Iterable[tuple[str, object]] | None = None, **extra, ) -> None: #: Normalized collection nodeid. @@ -275,7 +342,7 @@ class TestReport(BaseReport): #: 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, Optional[int], str] = location + self.location: tuple[str, int | None, str] = location #: A name -> value dictionary containing all keywords and #: markers associated with a test invocation. @@ -288,7 +355,7 @@ class TestReport(BaseReport): self.longrepr = longrepr #: One of 'setup', 'call', 'teardown' to indicate runtest phase. - self.when = when + self.when: Literal["setup", "call", "teardown"] = when #: User properties is a list of tuples (name, value) that holds user #: defined properties of the test. @@ -311,12 +378,10 @@ class TestReport(BaseReport): self.__dict__.update(extra) def __repr__(self) -> str: - return "<{} {!r} when={!r} outcome={!r}>".format( - self.__class__.__name__, self.nodeid, self.when, self.outcome - ) + 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": + 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. @@ -333,13 +398,13 @@ class TestReport(BaseReport): sections = [] if not call.excinfo: outcome: Literal["passed", "failed", "skipped"] = "passed" - longrepr: Union[ - None, - ExceptionInfo[BaseException], - Tuple[str, int, str], - str, - TerminalRepr, - ] = None + longrepr: ( + None + | ExceptionInfo[BaseException] + | tuple[str, int, str] + | str + | TerminalRepr + ) = None else: if not isinstance(excinfo, ExceptionInfo): outcome = "failed" @@ -347,23 +412,30 @@ class TestReport(BaseReport): 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." + 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 + longrepr = (os.fspath(path), line + 1, r.message) else: longrepr = (str(r.path), r.lineno, r.message) + elif isinstance(excinfo.value, BaseExceptionGroup) and ( + excinfo.value.split(skip.Exception)[1] is None + ): + # All exceptions in the group are skip exceptions. + outcome = "skipped" + excinfo = cast( + ExceptionInfo[ + BaseExceptionGroup[BaseException | BaseExceptionGroup] + ], + excinfo, + ) + longrepr = _format_exception_group_all_skipped_longrepr(item, excinfo) 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") - ) + longrepr = _format_failed_longrepr(item, call, excinfo) for rwhen, key, content in item._report_sections: sections.append((f"Captured {key} {rwhen}", content)) return cls( @@ -393,12 +465,14 @@ class CollectReport(BaseReport): def __init__( self, nodeid: str, - outcome: "Literal['passed', 'failed', 'skipped']", - longrepr: Union[ - None, ExceptionInfo[BaseException], Tuple[str, int, str], str, TerminalRepr - ], - result: Optional[List[Union[Item, Collector]]], - sections: Iterable[Tuple[str, 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. @@ -424,13 +498,11 @@ class CollectReport(BaseReport): @property def location( # type:ignore[override] self, - ) -> Optional[Tuple[str, Optional[int], str]]: + ) -> tuple[str, int | None, str] | None: return (self.fspath, None, self.fspath) def __repr__(self) -> str: - return "".format( - self.nodeid, len(self.result), self.outcome - ) + return f"" class CollectErrorRepr(TerminalRepr): @@ -442,9 +514,9 @@ class CollectErrorRepr(TerminalRepr): def pytest_report_to_serializable( - report: Union[CollectReport, TestReport] -) -> Optional[Dict[str, Any]]: - if isinstance(report, (TestReport, CollectReport)): + report: CollectReport | TestReport, +) -> dict[str, Any] | None: + if isinstance(report, TestReport | CollectReport): data = report._to_json() data["$report_type"] = report.__class__.__name__ return data @@ -453,8 +525,8 @@ def pytest_report_to_serializable( def pytest_report_from_serializable( - data: Dict[str, Any], -) -> Optional[Union[CollectReport, TestReport]]: + data: dict[str, Any], +) -> CollectReport | TestReport | None: if "$report_type" in data: if data["$report_type"] == "TestReport": return TestReport._from_json(data) @@ -466,7 +538,7 @@ def pytest_report_from_serializable( return None -def _report_to_json(report: BaseReport) -> Dict[str, Any]: +def _report_to_json(report: BaseReport) -> dict[str, Any]: """Return the contents of this report as a dict of builtin entries, suitable for serialization. @@ -474,8 +546,8 @@ def _report_to_json(report: BaseReport) -> Dict[str, Any]: """ def serialize_repr_entry( - entry: Union[ReprEntry, ReprEntryNative] - ) -> Dict[str, Any]: + entry: ReprEntry | ReprEntryNative, + ) -> dict[str, Any]: data = dataclasses.asdict(entry) for key, value in data.items(): if hasattr(value, "__dict__"): @@ -483,7 +555,7 @@ def _report_to_json(report: BaseReport) -> Dict[str, Any]: entry_data = {"type": type(entry).__name__, "data": data} return entry_data - def serialize_repr_traceback(reprtraceback: ReprTraceback) -> Dict[str, Any]: + def serialize_repr_traceback(reprtraceback: ReprTraceback) -> dict[str, Any]: result = dataclasses.asdict(reprtraceback) result["reprentries"] = [ serialize_repr_entry(x) for x in reprtraceback.reprentries @@ -491,18 +563,18 @@ def _report_to_json(report: BaseReport) -> Dict[str, Any]: return result def serialize_repr_crash( - reprcrash: Optional[ReprFileLocation], - ) -> Optional[Dict[str, Any]]: + 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]: + 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] = { + result: dict[str, Any] = { "reprcrash": serialize_repr_crash(longrepr.reprcrash), "reprtraceback": serialize_repr_traceback(longrepr.reprtraceback), "sections": longrepr.sections, @@ -539,7 +611,7 @@ def _report_to_json(report: BaseReport) -> Dict[str, Any]: return d -def _report_kwargs_from_json(reportdict: Dict[str, Any]) -> Dict[str, Any]: +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. @@ -560,7 +632,7 @@ def _report_kwargs_from_json(reportdict: Dict[str, Any]) -> Dict[str, Any]: if data["reprlocals"]: reprlocals = ReprLocals(data["reprlocals"]["lines"]) - reprentry: Union[ReprEntry, ReprEntryNative] = ReprEntry( + reprentry: ReprEntry | ReprEntryNative = ReprEntry( lines=data["lines"], reprfuncargs=reprfuncargs, reprlocals=reprlocals, @@ -579,7 +651,7 @@ def _report_kwargs_from_json(reportdict: Dict[str, Any]) -> Dict[str, Any]: ] return ReprTraceback(**repr_traceback_dict) - def deserialize_repr_crash(repr_crash_dict: Optional[Dict[str, Any]]): + def deserialize_repr_crash(repr_crash_dict: dict[str, Any] | None): if repr_crash_dict is not None: return ReprFileLocation(**repr_crash_dict) else: @@ -606,9 +678,9 @@ def _report_kwargs_from_json(reportdict: Dict[str, Any]) -> Dict[str, Any]: description, ) ) - exception_info: Union[ - ExceptionChainRepr, ReprExceptionInfo - ] = ExceptionChainRepr(chain) + exception_info: ExceptionChainRepr | ReprExceptionInfo = ExceptionChainRepr( + chain + ) else: exception_info = ReprExceptionInfo( reprtraceback=reprtraceback, diff --git a/venv/lib/python3.10/site-packages/_pytest/runner.py b/venv/lib/python3.10/site-packages/_pytest/runner.py index f861c05..9c20ff9 100644 --- a/venv/lib/python3.10/site-packages/_pytest/runner.py +++ b/venv/lib/python3.10/site-packages/_pytest/runner.py @@ -1,20 +1,22 @@ +# mypy: allow-untyped-defs """Basic collect and runtest protocol implementations.""" + +from __future__ import annotations + import bdb +from collections.abc import Callable import dataclasses import os import sys -from typing import Callable +import types from typing import cast -from typing import Dict +from typing import final from typing import Generic -from typing import List -from typing import Optional -from typing import Tuple -from typing import Type +from typing import Literal from typing import TYPE_CHECKING from typing import TypeVar -from typing import Union +from .config import Config from .reports import BaseReport from .reports import CollectErrorRepr from .reports import CollectReport @@ -23,10 +25,10 @@ from _pytest import timing from _pytest._code.code import ExceptionChainRepr from _pytest._code.code import ExceptionInfo from _pytest._code.code import TerminalRepr -from _pytest.compat import final 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 @@ -34,12 +36,11 @@ from _pytest.outcomes import OutcomeException from _pytest.outcomes import Skipped from _pytest.outcomes import TEST_OUTCOME -if sys.version_info[:2] < (3, 11): + +if sys.version_info < (3, 11): from exceptiongroup import BaseExceptionGroup if TYPE_CHECKING: - from typing_extensions import Literal - from _pytest.main import Session from _pytest.terminal import TerminalReporter @@ -61,19 +62,21 @@ def pytest_addoption(parser: Parser) -> None: "--durations-min", action="store", type=float, - default=0.005, + default=None, metavar="N", help="Minimal duration in seconds for inclusion in slowest list. " - "Default: 0.005.", + "Default: 0.005 (or 0.0 if -vv is given).", ) -def pytest_terminal_summary(terminalreporter: "TerminalReporter") -> None: +def pytest_terminal_summary(terminalreporter: TerminalReporter) -> None: durations = terminalreporter.config.option.durations durations_min = terminalreporter.config.option.durations_min - verbose = terminalreporter.config.getvalue("verbose") + verbose = terminalreporter.config.get_verbosity() if durations is None: return + if durations_min is None: + durations_min = 0.005 if verbose < 2 else 0.0 tr = terminalreporter dlist = [] for replist in tr.stats.values(): @@ -82,33 +85,34 @@ def pytest_terminal_summary(terminalreporter: "TerminalReporter") -> None: dlist.append(rep) if not dlist: return - dlist.sort(key=lambda x: x.duration, reverse=True) # type: ignore[no-any-return] + dlist.sort(key=lambda x: x.duration, reverse=True) if not durations: tr.write_sep("=", "slowest durations") else: - tr.write_sep("=", "slowest %s durations" % durations) + tr.write_sep("=", f"slowest {durations} durations") dlist = dlist[:durations] for i, rep in enumerate(dlist): - if verbose < 2 and rep.duration < durations_min: + if rep.duration < durations_min: tr.write_line("") - tr.write_line( - "(%s durations < %gs hidden. Use -vv to show these durations.)" - % (len(dlist) - i, durations_min) - ) + message = f"({len(dlist) - i} durations < {durations_min:g}s hidden." + if terminalreporter.config.option.durations_min is None: + message += " Use -vv to show these durations." + message += ")" + tr.write_line(message) break tr.write_line(f"{rep.duration:02.2f}s {rep.when:<8} {rep.nodeid}") -def pytest_sessionstart(session: "Session") -> None: +def pytest_sessionstart(session: Session) -> None: session._setupstate = SetupState() -def pytest_sessionfinish(session: "Session") -> None: +def pytest_sessionfinish(session: Session) -> None: session._setupstate.teardown_exact(None) -def pytest_runtest_protocol(item: Item, nextitem: Optional[Item]) -> bool: +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) @@ -117,8 +121,8 @@ def pytest_runtest_protocol(item: Item, nextitem: Optional[Item]) -> bool: def runtestprotocol( - item: Item, log: bool = True, nextitem: Optional[Item] = None -) -> List[TestReport]: + 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 @@ -131,6 +135,10 @@ def runtestprotocol( 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. @@ -163,6 +171,8 @@ def pytest_runtest_call(item: Item) -> None: 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: @@ -171,20 +181,22 @@ def pytest_runtest_call(item: Item) -> None: # 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 e + raise -def pytest_runtest_teardown(item: Item, nextitem: Optional[Item]) -> None: +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: Optional["Literal['setup', 'call', 'teardown']"] + item: Item, when: Literal["setup", "call", "teardown"] | None ) -> None: """Update :envvar:`PYTEST_CURRENT_TEST` to reflect the current item and stage. @@ -200,7 +212,7 @@ def _update_current_test_var( os.environ.pop(var_name) -def pytest_report_teststatus(report: BaseReport) -> Optional[Tuple[str, str, str]]: +def pytest_report_teststatus(report: BaseReport) -> tuple[str, str, str] | None: if report.when in ("setup", "teardown"): if report.failed: # category, shortletter, verbose-word @@ -217,19 +229,40 @@ def pytest_report_teststatus(report: BaseReport) -> Optional[Tuple[str, str, str def call_and_report( - item: Item, when: "Literal['setup', 'call', 'teardown']", log: bool = True, **kwds + item: Item, when: Literal["setup", "call", "teardown"], log: bool = True, **kwds ) -> TestReport: - call = call_runtest_hook(item, when, **kwds) - hook = item.ihook - report: TestReport = hook.pytest_runtest_makereport(item=item, call=call) + 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}" + + call = CallInfo.from_call( + lambda: runtest_hook(item=item, **kwds), + when=when, + reraise=get_reraise_exceptions(item.config), + ) + report: TestReport = ihook.pytest_runtest_makereport(item=item, call=call) if log: - hook.pytest_runtest_logreport(report=report) + ihook.pytest_runtest_logreport(report=report) if check_interactive_exception(call, report): - hook.pytest_exception_interact(node=item, call=call, report=report) + ihook.pytest_exception_interact(node=item, call=call, report=report) return report -def check_interactive_exception(call: "CallInfo[object]", report: BaseReport) -> bool: +def get_reraise_exceptions(config: Config) -> tuple[type[BaseException], ...]: + """Return exception types that should not be suppressed in general.""" + reraise: tuple[type[BaseException], ...] = (Exit,) + if not config.getoption("usepdb", False): + reraise += (KeyboardInterrupt,) + return reraise + + +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: @@ -238,31 +271,12 @@ def check_interactive_exception(call: "CallInfo[object]", report: BaseReport) -> if hasattr(report, "wasxfail"): # Exception was expected. return False - if isinstance(call.excinfo.value, (Skipped, bdb.BdbQuit)): + if isinstance(call.excinfo.value, Skipped | bdb.BdbQuit): # Special control flow exception. return False return True -def call_runtest_hook( - item: Item, when: "Literal['setup', 'call', 'teardown']", **kwds -) -> "CallInfo[None]": - if when == "setup": - ihook: Callable[..., None] = item.ihook.pytest_runtest_setup - elif when == "call": - ihook = item.ihook.pytest_runtest_call - elif when == "teardown": - ihook = item.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,) - return CallInfo.from_call( - lambda: ihook(item=item, **kwds), when=when, reraise=reraise - ) - - TResult = TypeVar("TResult", covariant=True) @@ -271,9 +285,9 @@ TResult = TypeVar("TResult", covariant=True) class CallInfo(Generic[TResult]): """Result/Exception info of a function invocation.""" - _result: Optional[TResult] + _result: TResult | None #: The captured exception of the call, if it raised. - excinfo: Optional[ExceptionInfo[BaseException]] + 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. @@ -281,16 +295,16 @@ class CallInfo(Generic[TResult]): #: The call duration, in seconds. duration: float #: The context of invocation: "collect", "setup", "call" or "teardown". - when: "Literal['collect', 'setup', 'call', 'teardown']" + when: Literal["collect", "setup", "call", "teardown"] def __init__( self, - result: Optional[TResult], - excinfo: Optional[ExceptionInfo[BaseException]], + result: TResult | None, + excinfo: ExceptionInfo[BaseException] | None, start: float, stop: float, duration: float, - when: "Literal['collect', 'setup', 'call', 'teardown']", + when: Literal["collect", "setup", "call", "teardown"], *, _ispytest: bool = False, ) -> None: @@ -318,16 +332,15 @@ class CallInfo(Generic[TResult]): @classmethod def from_call( cls, - func: "Callable[[], TResult]", - when: "Literal['collect', 'setup', 'call', 'teardown']", - reraise: Optional[ - Union[Type[BaseException], Tuple[Type[BaseException], ...]] - ] = None, - ) -> "CallInfo[TResult]": + 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: @@ -335,23 +348,19 @@ class CallInfo(Generic[TResult]): function, instead of being wrapped in the CallInfo. """ excinfo = None - start = timing.time() - precise_start = timing.perf_counter() + instant = timing.Instant() try: - result: Optional[TResult] = func() + 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() + duration = instant.elapsed() return cls( - start=start, - stop=stop, - duration=duration, + start=duration.start.time, + stop=duration.stop.time, + duration=duration.seconds, when=when, result=result, excinfo=excinfo, @@ -369,16 +378,36 @@ def pytest_runtest_makereport(item: Item, call: CallInfo[None]) -> TestReport: def pytest_make_collect_report(collector: Collector) -> CollectReport: - call = CallInfo.from_call(lambda: list(collector.collect()), "collect") - longrepr: Union[None, Tuple[str, int, str], str, TerminalRepr] = None + 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: - # Type ignored because unittest is loaded dynamically. - skip_exceptions.append(unittest.SkipTest) # type: ignore + skip_exceptions.append(unittest.SkipTest) if isinstance(call.excinfo.value, tuple(skip_exceptions)): outcome = "skipped" r_ = collector._repr_failure_py(call.excinfo, "line") @@ -465,13 +494,13 @@ class SetupState: def __init__(self) -> None: # The stack is in the dict insertion order. - self.stack: Dict[ + self.stack: dict[ Node, - Tuple[ + tuple[ # Node's finalizers. - List[Callable[[], object]], - # Node's exception, if its setup raised. - Optional[Union[OutcomeException, Exception]], + list[Callable[[], object]], + # Node's exception and original traceback, if its setup raised. + tuple[OutcomeException | Exception, types.TracebackType | None] | None, ], ] = {} @@ -484,7 +513,7 @@ class SetupState: for col, (finalizers, exc) in self.stack.items(): assert col in needed_collectors, "previous item was not torn down properly" if exc: - raise exc + raise exc[0].with_traceback(exc[1]) for col in needed_collectors[len(self.stack) :]: assert col not in self.stack @@ -493,8 +522,8 @@ class SetupState: try: col.setup() except TEST_OUTCOME as exc: - self.stack[col] = (self.stack[col][0], exc) - raise 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. @@ -506,15 +535,15 @@ class SetupState: assert node in self.stack, (node, self.stack) self.stack[node][0].append(finalizer) - def teardown_exact(self, nextitem: Optional[Item]) -> None: + 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] = [] + needed_collectors = (nextitem and nextitem.listchain()) or [] + exceptions: list[BaseException] = [] while self.stack: if list(self.stack.keys()) == needed_collectors[: len(self.stack)]: break diff --git a/venv/lib/python3.10/site-packages/_pytest/scope.py b/venv/lib/python3.10/site-packages/_pytest/scope.py index 7a746fb..2b007e8 100644 --- a/venv/lib/python3.10/site-packages/_pytest/scope.py +++ b/venv/lib/python3.10/site-packages/_pytest/scope.py @@ -7,15 +7,15 @@ 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 Optional -from typing import TYPE_CHECKING +from typing import Literal -if TYPE_CHECKING: - from typing_extensions import Literal - _ScopeName = Literal["session", "package", "module", "class", "function"] +_ScopeName = Literal["session", "package", "module", "class", "function"] @total_ordering @@ -33,35 +33,35 @@ class Scope(Enum): """ # Scopes need to be listed from lower to higher. - Function: "_ScopeName" = "function" - Class: "_ScopeName" = "class" - Module: "_ScopeName" = "module" - Package: "_ScopeName" = "package" - Session: "_ScopeName" = "session" + Function = "function" + Class = "class" + Module = "module" + Package = "package" + Session = "session" - def next_lower(self) -> "Scope": + 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": + 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: + 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: Optional[str] = None - ) -> "Scope": + 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. diff --git a/venv/lib/python3.10/site-packages/_pytest/setuponly.py b/venv/lib/python3.10/site-packages/_pytest/setuponly.py index 583590d..7e6b46b 100644 --- a/venv/lib/python3.10/site-packages/_pytest/setuponly.py +++ b/venv/lib/python3.10/site-packages/_pytest/setuponly.py @@ -1,8 +1,7 @@ -from typing import Generator -from typing import Optional -from typing import Union +from __future__ import annotations + +from collections.abc import Generator -import pytest from _pytest._io.saferepr import saferepr from _pytest.config import Config from _pytest.config import ExitCode @@ -10,6 +9,7 @@ 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: @@ -28,37 +28,42 @@ def pytest_addoption(parser: Parser) -> None: ) -@pytest.hookimpl(hookwrapper=True) +@pytest.hookimpl(wrapper=True) def pytest_fixture_setup( fixturedef: FixtureDef[object], request: SubRequest -) -> Generator[None, None, None]: - yield - 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) +) -> 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 = fixturedef.ids[request.param_index] - else: - param = request.param - fixturedef.cached_param = param # type: ignore[attr-defined] - _show_fixture_action(fixturedef, "SETUP") + 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]) -> None: +def pytest_fixture_post_finalizer( + fixturedef: FixtureDef[object], request: SubRequest +) -> None: if fixturedef.cached_result is not None: - config = fixturedef._fixturemanager.config + config = request.config if config.option.setupshow: - _show_fixture_action(fixturedef, "TEARDOWN") + _show_fixture_action(fixturedef, request.config, "TEARDOWN") if hasattr(fixturedef, "cached_param"): - del fixturedef.cached_param # type: ignore[attr-defined] + del fixturedef.cached_param -def _show_fixture_action(fixturedef: FixtureDef[object], msg: str) -> None: - config = fixturedef._fixturemanager.config +def _show_fixture_action( + fixturedef: FixtureDef[object], config: Config, msg: str +) -> None: capman = config.pluginmanager.getplugin("capturemanager") if capman: capman.suspend_global_capture() @@ -68,13 +73,9 @@ def _show_fixture_action(fixturedef: FixtureDef[object], msg: str) -> None: # 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( - step=msg.ljust(8), # align the output to TEARDOWN - scope=fixturedef.scope[0].upper(), - fixture=fixturedef.argname, - ) - ) + + scopename = fixturedef.scope[0].upper() + tw.write(f"{msg:<8} {scopename} {fixturedef.argname}") if msg == "SETUP": deps = sorted(arg for arg in fixturedef.argnames if arg != "request") @@ -82,7 +83,7 @@ def _show_fixture_action(fixturedef: FixtureDef[object], msg: str) -> None: tw.write(" (fixtures used: {})".format(", ".join(deps))) if hasattr(fixturedef, "cached_param"): - tw.write(f"[{saferepr(fixturedef.cached_param, maxsize=42)}]") # type: ignore[attr-defined] + tw.write(f"[{saferepr(fixturedef.cached_param, maxsize=42)}]") tw.flush() @@ -91,7 +92,7 @@ def _show_fixture_action(fixturedef: FixtureDef[object], msg: str) -> None: @pytest.hookimpl(tryfirst=True) -def pytest_cmdline_main(config: Config) -> Optional[Union[int, ExitCode]]: +def pytest_cmdline_main(config: Config) -> int | ExitCode | None: if config.option.setuponly: config.option.setupshow = True return None diff --git a/venv/lib/python3.10/site-packages/_pytest/setupplan.py b/venv/lib/python3.10/site-packages/_pytest/setupplan.py index 1a4ebdd..4e124cc 100644 --- a/venv/lib/python3.10/site-packages/_pytest/setupplan.py +++ b/venv/lib/python3.10/site-packages/_pytest/setupplan.py @@ -1,12 +1,11 @@ -from typing import Optional -from typing import Union +from __future__ import annotations -import pytest 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: @@ -23,7 +22,7 @@ def pytest_addoption(parser: Parser) -> None: @pytest.hookimpl(tryfirst=True) def pytest_fixture_setup( fixturedef: FixtureDef[object], request: SubRequest -) -> Optional[object]: +) -> 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) @@ -33,7 +32,7 @@ def pytest_fixture_setup( @pytest.hookimpl(tryfirst=True) -def pytest_cmdline_main(config: Config) -> Optional[Union[int, ExitCode]]: +def pytest_cmdline_main(config: Config) -> int | ExitCode | None: if config.option.setupplan: config.option.setuponly = True config.option.setupshow = True diff --git a/venv/lib/python3.10/site-packages/_pytest/skipping.py b/venv/lib/python3.10/site-packages/_pytest/skipping.py index 26ce737..3b06762 100644 --- a/venv/lib/python3.10/site-packages/_pytest/skipping.py +++ b/venv/lib/python3.10/site-packages/_pytest/skipping.py @@ -1,14 +1,15 @@ +# mypy: allow-untyped-defs """Support for skip/xfail functions and markers.""" + +from __future__ import annotations + +from collections.abc import Generator +from collections.abc import Mapping import dataclasses import os import platform import sys import traceback -from collections.abc import Mapping -from typing import Generator -from typing import Optional -from typing import Tuple -from typing import Type from _pytest.config import Config from _pytest.config import hookimpl @@ -18,7 +19,9 @@ from _pytest.nodes import Item from _pytest.outcomes import fail from _pytest.outcomes import skip from _pytest.outcomes import xfail +from _pytest.raises import AbstractRaises from _pytest.reports import BaseReport +from _pytest.reports import TestReport from _pytest.runner import CallInfo from _pytest.stash import StashKey @@ -34,11 +37,13 @@ def pytest_addoption(parser: Parser) -> None: ) parser.addini( - "xfail_strict", + "strict_xfail", "Default for the strict parameter of xfail " - "markers when not given explicitly (default: False)", - default=False, + "markers when not given explicitly (default: False) (alias: xfail_strict)", type="bool", + # None => fallback to `strict`. + default=None, + aliases=["xfail_strict"], ) @@ -71,7 +76,7 @@ def pytest_configure(config: Config) -> None: ) config.addinivalue_line( "markers", - "xfail(condition, ..., *, reason=..., run=True, raises=None, strict=xfail_strict): " + "xfail(condition, ..., *, reason=..., run=True, raises=None, strict=strict_xfail): " "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. " @@ -81,7 +86,7 @@ def pytest_configure(config: Config) -> None: ) -def evaluate_condition(item: Item, mark: Mark, condition: object) -> Tuple[bool, str]: +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 @@ -103,20 +108,18 @@ def evaluate_condition(item: Item, mark: Mark, condition: object) -> Tuple[bool, ): if not isinstance(dictionary, Mapping): raise ValueError( - "pytest_markeval_namespace() needs to return a dict, got {!r}".format( - dictionary - ) + f"pytest_markeval_namespace() needs to return a dict, got {dictionary!r}" ) globals_.update(dictionary) if hasattr(item, "obj"): - globals_.update(item.obj.__globals__) # type: ignore[attr-defined] + 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 = [ - "Error evaluating %r condition" % mark.name, + f"Error evaluating {mark.name!r} condition", " " + condition, " " + " " * (exc.offset or 0) + "^", "SyntaxError: invalid syntax", @@ -124,7 +127,7 @@ def evaluate_condition(item: Item, mark: Mark, condition: object) -> Tuple[bool, fail("\n".join(msglines), pytrace=False) except Exception as exc: msglines = [ - "Error evaluating %r condition" % mark.name, + f"Error evaluating {mark.name!r} condition", " " + condition, *traceback.format_exception_only(type(exc), exc), ] @@ -136,7 +139,7 @@ def evaluate_condition(item: Item, mark: Mark, condition: object) -> Tuple[bool, result = bool(condition) except Exception as exc: msglines = [ - "Error evaluating %r condition as a boolean" % mark.name, + f"Error evaluating {mark.name!r} condition as a boolean", *traceback.format_exception_only(type(exc), exc), ] fail("\n".join(msglines), pytrace=False) @@ -148,7 +151,7 @@ def evaluate_condition(item: Item, mark: Mark, condition: object) -> Tuple[bool, else: # XXX better be checked at collection time msg = ( - "Error evaluating %r: " % mark.name + f"Error evaluating {mark.name!r}: " + "you need to specify reason=STRING when using booleans as conditions." ) fail(msg, pytrace=False) @@ -163,7 +166,7 @@ class Skip: reason: str = "unconditional skip" -def evaluate_skip_marks(item: Item) -> Optional[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: @@ -195,19 +198,28 @@ def evaluate_skip_marks(item: Item) -> Optional[Skip]: class Xfail: """The result of evaluate_xfail_marks().""" - __slots__ = ("reason", "run", "strict", "raises") + __slots__ = ("raises", "reason", "run", "strict") reason: str run: bool strict: bool - raises: Optional[Tuple[Type[BaseException], ...]] + raises: ( + type[BaseException] + | tuple[type[BaseException], ...] + | AbstractRaises[BaseException] + | None + ) -def evaluate_xfail_marks(item: Item) -> Optional[Xfail]: +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")) + strict = mark.kwargs.get("strict") + if strict is None: + strict = item.config.getini("strict_xfail") + if strict is None: + strict = item.config.getini("strict") raises = mark.kwargs.get("raises", None) if "condition" not in mark.kwargs: conditions = mark.args @@ -229,7 +241,7 @@ def evaluate_xfail_marks(item: Item) -> Optional[Xfail]: # Saves the xfail mark evaluation. Can be refreshed during call if None. -xfailed_key = StashKey[Optional[Xfail]]() +xfailed_key = StashKey[Xfail | None]() @hookimpl(tryfirst=True) @@ -243,8 +255,8 @@ def pytest_runtest_setup(item: Item) -> None: xfail("[NOTRUN] " + xfailed.reason) -@hookimpl(hookwrapper=True) -def pytest_runtest_call(item: Item) -> Generator[None, None, None]: +@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) @@ -252,33 +264,44 @@ def pytest_runtest_call(item: Item) -> Generator[None, None, None]: if xfailed and not item.config.option.runxfail and not xfailed.run: xfail("[NOTRUN] " + xfailed.reason) - yield - - # 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) + 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(hookwrapper=True) -def pytest_runtest_makereport(item: Item, call: CallInfo[None]): - outcome = yield - rep = outcome.get_result() +@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.wasxfail = 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: + if raises is None or ( + ( + isinstance(raises, type | tuple) + and isinstance(call.excinfo.value, raises) + ) + or ( + isinstance(raises, AbstractRaises) + and raises.matches(call.excinfo.value) + ) + ): rep.outcome = "skipped" rep.wasxfail = xfailed.reason + else: + rep.outcome = "failed" elif call.when == "call": if xfailed.strict: rep.outcome = "failed" @@ -286,9 +309,10 @@ def pytest_runtest_makereport(item: Item, call: CallInfo[None]): else: rep.outcome = "passed" rep.wasxfail = xfailed.reason + return rep -def pytest_report_teststatus(report: BaseReport) -> Optional[Tuple[str, str, str]]: +def pytest_report_teststatus(report: BaseReport) -> tuple[str, str, str] | None: if hasattr(report, "wasxfail"): if report.skipped: return "xfailed", "x", "XFAIL" diff --git a/venv/lib/python3.10/site-packages/_pytest/stash.py b/venv/lib/python3.10/site-packages/_pytest/stash.py index e61d75b..6a9ff88 100644 --- a/venv/lib/python3.10/site-packages/_pytest/stash.py +++ b/venv/lib/python3.10/site-packages/_pytest/stash.py @@ -1,9 +1,9 @@ +from __future__ import annotations + from typing import Any from typing import cast -from typing import Dict from typing import Generic from typing import TypeVar -from typing import Union __all__ = ["Stash", "StashKey"] @@ -19,6 +19,8 @@ class StashKey(Generic[T]): 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__ = () @@ -61,12 +63,14 @@ class Stash: 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] = {} + self._storage: dict[StashKey[Any], object] = {} def __setitem__(self, key: StashKey[T], value: T) -> None: """Set a value for key.""" @@ -79,7 +83,7 @@ class Stash: """ return cast(T, self._storage[key]) - def get(self, key: StashKey[T], default: D) -> Union[T, D]: + 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: diff --git a/venv/lib/python3.10/site-packages/_pytest/stepwise.py b/venv/lib/python3.10/site-packages/_pytest/stepwise.py index 74ad9db..8901540 100644 --- a/venv/lib/python3.10/site-packages/_pytest/stepwise.py +++ b/venv/lib/python3.10/site-packages/_pytest/stepwise.py @@ -1,16 +1,21 @@ -from typing import List -from typing import Optional +from __future__ import annotations + +import dataclasses +from datetime import datetime +from datetime import timedelta +from typing import Any from typing import TYPE_CHECKING -import pytest 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 + if TYPE_CHECKING: - from _pytest.cacheprovider import Cache + from typing_extensions import Self STEPWISE_CACHE_DIR = "cache/stepwise" @@ -34,12 +39,20 @@ def pytest_addoption(parser: Parser) -> None: help="Ignore the first failing test but stop on the next failing test. " "Implicitly enables --stepwise.", ) + group.addoption( + "--sw-reset", + "--stepwise-reset", + action="store_true", + default=False, + dest="stepwise_reset", + help="Resets stepwise state, restarting the stepwise workflow. " + "Implicitly enables --stepwise.", + ) -@pytest.hookimpl def pytest_configure(config: Config) -> None: - if config.option.stepwise_skip: - # allow --stepwise-skip to work on it's own merits. + # --stepwise-skip/--stepwise-reset implies stepwise. + if config.option.stepwise_skip or config.option.stepwise_reset: config.option.stepwise = True if config.getoption("stepwise"): config.pluginmanager.register(StepwisePlugin(config), "stepwiseplugin") @@ -52,43 +65,108 @@ def pytest_sessionfinish(session: Session) -> None: # 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, []) + + +@dataclasses.dataclass +class StepwiseCacheInfo: + # The nodeid of the last failed test. + last_failed: str | None + + # The number of tests in the last time --stepwise was run. + # We use this information as a simple way to invalidate the cache information, avoiding + # confusing behavior in case the cache is stale. + last_test_count: int | None + + # The date when the cache was last updated, for information purposes only. + last_cache_date_str: str + + @property + def last_cache_date(self) -> datetime: + return datetime.fromisoformat(self.last_cache_date_str) + + @classmethod + def empty(cls) -> Self: + return cls( + last_failed=None, + last_test_count=None, + last_cache_date_str=datetime.now().isoformat(), + ) + + def update_date_to_now(self) -> None: + self.last_cache_date_str = datetime.now().isoformat() class StepwisePlugin: def __init__(self, config: Config) -> None: self.config = config - self.session: Optional[Session] = None - self.report_status = "" + self.session: Session | None = None + self.report_status: list[str] = [] assert config.cache is not None self.cache: Cache = config.cache - self.lastfailed: Optional[str] = self.cache.get(STEPWISE_CACHE_DIR, None) self.skip: bool = config.getoption("stepwise_skip") + self.reset: bool = config.getoption("stepwise_reset") + self.cached_info = self._load_cached_info() + + def _load_cached_info(self) -> StepwiseCacheInfo: + cached_dict: dict[str, Any] | None = self.cache.get(STEPWISE_CACHE_DIR, None) + if cached_dict: + try: + return StepwiseCacheInfo( + cached_dict["last_failed"], + cached_dict["last_test_count"], + cached_dict["last_cache_date_str"], + ) + except (KeyError, TypeError) as e: + error = f"{type(e).__name__}: {e}" + self.report_status.append(f"error reading cache, discarding ({error})") + + # Cache not found or error during load, return a new cache. + return StepwiseCacheInfo.empty() def pytest_sessionstart(self, session: Session) -> None: self.session = session def pytest_collection_modifyitems( - self, config: Config, items: List[nodes.Item] + self, config: Config, items: list[nodes.Item] ) -> None: - if not self.lastfailed: - self.report_status = "no previously failed tests, not skipping." + last_test_count = self.cached_info.last_test_count + self.cached_info.last_test_count = len(items) + + if self.reset: + self.report_status.append("resetting state, not skipping.") + self.cached_info.last_failed = None return - # check all item nodes until we find a match on last failed + if not self.cached_info.last_failed: + self.report_status.append("no previously failed tests, not skipping.") + return + + if last_test_count is not None and last_test_count != len(items): + self.report_status.append( + f"test count changed, not skipping (now {len(items)} tests, previously {last_test_count})." + ) + self.cached_info.last_failed = None + 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: + if item.nodeid == self.cached_info.last_failed: 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." + self.report_status.append("previously failed test not found, not skipping.") else: - self.report_status = f"skipping {failed_index} already passed items." + cache_age = datetime.now() - self.cached_info.last_cache_date + # Round up to avoid showing microseconds. + cache_age = timedelta(seconds=int(cache_age.total_seconds())) + self.report_status.append( + f"skipping {failed_index} already passed items (cache from {cache_age} ago," + f" use --sw-reset to discard)." + ) deselected = items[:failed_index] del items[:failed_index] config.hook.pytest_deselected(items=deselected) @@ -98,13 +176,13 @@ class StepwisePlugin: 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 + if report.nodeid == self.cached_info.last_failed: + self.cached_info.last_failed = None self.skip = False else: # Mark test as the last failing and interrupt the test session. - self.lastfailed = report.nodeid + self.cached_info.last_failed = report.nodeid assert self.session is not None self.session.shouldstop = ( "Test failed, continuing from this test next run." @@ -114,12 +192,12 @@ class StepwisePlugin: # 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 + if report.nodeid == self.cached_info.last_failed: + self.cached_info.last_failed = None - def pytest_report_collectionfinish(self) -> Optional[str]: - if self.config.getoption("verbose") >= 0 and self.report_status: - return f"stepwise: {self.report_status}" + def pytest_report_collectionfinish(self) -> list[str] | None: + if self.config.get_verbosity() >= 0 and self.report_status: + return [f"stepwise: {x}" for x in self.report_status] return None def pytest_sessionfinish(self) -> None: @@ -127,4 +205,5 @@ class StepwisePlugin: # 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) + self.cached_info.update_date_to_now() + self.cache.set(STEPWISE_CACHE_DIR, dataclasses.asdict(self.cached_info)) diff --git a/venv/lib/python3.10/site-packages/_pytest/subtests.py b/venv/lib/python3.10/site-packages/_pytest/subtests.py new file mode 100644 index 0000000..e0ceb27 --- /dev/null +++ b/venv/lib/python3.10/site-packages/_pytest/subtests.py @@ -0,0 +1,411 @@ +"""Builtin plugin that adds subtests support.""" + +from __future__ import annotations + +from collections import defaultdict +from collections.abc import Callable +from collections.abc import Iterator +from collections.abc import Mapping +from contextlib import AbstractContextManager +from contextlib import contextmanager +from contextlib import ExitStack +from contextlib import nullcontext +import dataclasses +import time +from types import TracebackType +from typing import Any +from typing import TYPE_CHECKING + +import pluggy + +from _pytest._code import ExceptionInfo +from _pytest._io.saferepr import saferepr +from _pytest.capture import CaptureFixture +from _pytest.capture import FDCapture +from _pytest.capture import SysCapture +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.logging import catching_logs +from _pytest.logging import LogCaptureHandler +from _pytest.logging import LoggingPlugin +from _pytest.reports import TestReport +from _pytest.runner import CallInfo +from _pytest.runner import check_interactive_exception +from _pytest.runner import get_reraise_exceptions +from _pytest.stash import StashKey + + +if TYPE_CHECKING: + from typing_extensions import Self + + +def pytest_addoption(parser: Parser) -> None: + Config._add_verbosity_ini( + parser, + Config.VERBOSITY_SUBTESTS, + help=( + "Specify verbosity level for subtests. " + "Higher levels will generate output for passed subtests. Failed subtests are always reported." + ), + ) + + +@dataclasses.dataclass(frozen=True, slots=True, kw_only=True) +class SubtestContext: + """The values passed to Subtests.test() that are included in the test report.""" + + msg: str | None + kwargs: Mapping[str, Any] + + def _to_json(self) -> dict[str, Any]: + return dataclasses.asdict(self) + + @classmethod + def _from_json(cls, d: dict[str, Any]) -> Self: + return cls(msg=d["msg"], kwargs=d["kwargs"]) + + +@dataclasses.dataclass(init=False) +class SubtestReport(TestReport): + context: SubtestContext + + @property + def head_line(self) -> str: + _, _, domain = self.location + return f"{domain} {self._sub_test_description()}" + + def _sub_test_description(self) -> str: + parts = [] + if self.context.msg is not None: + parts.append(f"[{self.context.msg}]") + if self.context.kwargs: + params_desc = ", ".join( + f"{k}={saferepr(v)}" for (k, v) in self.context.kwargs.items() + ) + parts.append(f"({params_desc})") + return " ".join(parts) or "()" + + def _to_json(self) -> dict[str, Any]: + data = super()._to_json() + del data["context"] + data["_report_type"] = "SubTestReport" + data["_subtest.context"] = self.context._to_json() + return data + + @classmethod + def _from_json(cls, reportdict: dict[str, Any]) -> SubtestReport: + report = super()._from_json(reportdict) + report.context = SubtestContext._from_json(reportdict["_subtest.context"]) + return report + + @classmethod + def _new( + cls, + test_report: TestReport, + context: SubtestContext, + captured_output: Captured | None, + captured_logs: CapturedLogs | None, + ) -> Self: + result = super()._from_json(test_report._to_json()) + result.context = context + + if captured_output: + if captured_output.out: + result.sections.append(("Captured stdout call", captured_output.out)) + if captured_output.err: + result.sections.append(("Captured stderr call", captured_output.err)) + + if captured_logs and (log := captured_logs.handler.stream.getvalue()): + result.sections.append(("Captured log call", log)) + + return result + + +@fixture +def subtests(request: SubRequest) -> Subtests: + """Provides subtests functionality.""" + capmam = request.node.config.pluginmanager.get_plugin("capturemanager") + suspend_capture_ctx = ( + capmam.global_and_fixture_disabled if capmam is not None else nullcontext + ) + return Subtests(request.node.ihook, suspend_capture_ctx, request, _ispytest=True) + + +class Subtests: + """Subtests fixture, enables declaring subtests inside test functions via the :meth:`test` method.""" + + def __init__( + self, + ihook: pluggy.HookRelay, + suspend_capture_ctx: Callable[[], AbstractContextManager[None]], + request: SubRequest, + *, + _ispytest: bool = False, + ) -> None: + check_ispytest(_ispytest) + self._ihook = ihook + self._suspend_capture_ctx = suspend_capture_ctx + self._request = request + + def test( + self, + msg: str | None = None, + **kwargs: Any, + ) -> _SubTestContextManager: + """ + Context manager for subtests, capturing exceptions raised inside the subtest scope and + reporting assertion failures and errors individually. + + Usage + ----- + + .. code-block:: python + + def test(subtests): + for i in range(5): + with subtests.test("custom message", i=i): + assert i % 2 == 0 + + :param msg: + If given, the message will be shown in the test report in case of subtest failure. + + :param kwargs: + Arbitrary values that are also added to the subtest report. + """ + return _SubTestContextManager( + self._ihook, + msg, + kwargs, + request=self._request, + suspend_capture_ctx=self._suspend_capture_ctx, + config=self._request.config, + ) + + +@dataclasses.dataclass +class _SubTestContextManager: + """ + Context manager for subtests, capturing exceptions raised inside the subtest scope and handling + them through the pytest machinery. + """ + + # Note: initially the logic for this context manager was implemented directly + # in Subtests.test() as a @contextmanager, however, it is not possible to control the output fully when + # exiting from it due to an exception when in `--exitfirst` mode, so this was refactored into an + # explicit context manager class (pytest-dev/pytest-subtests#134). + + ihook: pluggy.HookRelay + msg: str | None + kwargs: dict[str, Any] + suspend_capture_ctx: Callable[[], AbstractContextManager[None]] + request: SubRequest + config: Config + + def __enter__(self) -> None: + __tracebackhide__ = True + + self._start = time.time() + self._precise_start = time.perf_counter() + self._exc_info = None + + self._exit_stack = ExitStack() + self._captured_output = self._exit_stack.enter_context( + capturing_output(self.request) + ) + self._captured_logs = self._exit_stack.enter_context( + capturing_logs(self.request) + ) + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> bool: + __tracebackhide__ = True + if exc_val is not None: + exc_info = ExceptionInfo.from_exception(exc_val) + else: + exc_info = None + + self._exit_stack.close() + + precise_stop = time.perf_counter() + duration = precise_stop - self._precise_start + stop = time.time() + + call_info = CallInfo[None]( + None, + exc_info, + start=self._start, + stop=stop, + duration=duration, + when="call", + _ispytest=True, + ) + report = self.ihook.pytest_runtest_makereport( + item=self.request.node, call=call_info + ) + sub_report = SubtestReport._new( + report, + SubtestContext(msg=self.msg, kwargs=self.kwargs), + captured_output=self._captured_output, + captured_logs=self._captured_logs, + ) + + if sub_report.failed: + failed_subtests = self.config.stash[failed_subtests_key] + failed_subtests[self.request.node.nodeid] += 1 + + with self.suspend_capture_ctx(): + self.ihook.pytest_runtest_logreport(report=sub_report) + + if check_interactive_exception(call_info, sub_report): + self.ihook.pytest_exception_interact( + node=self.request.node, call=call_info, report=sub_report + ) + + if exc_val is not None: + if isinstance(exc_val, get_reraise_exceptions(self.config)): + return False + if self.request.session.shouldfail: + return False + return True + + +@contextmanager +def capturing_output(request: SubRequest) -> Iterator[Captured]: + option = request.config.getoption("capture", None) + + capman = request.config.pluginmanager.getplugin("capturemanager") + if getattr(capman, "_capture_fixture", None): + # capsys or capfd are active, subtest should not capture. + fixture = None + elif option == "sys": + fixture = CaptureFixture(SysCapture, request, _ispytest=True) + elif option == "fd": + fixture = CaptureFixture(FDCapture, request, _ispytest=True) + else: + fixture = None + + if fixture is not None: + fixture._start() + + captured = Captured() + try: + yield captured + finally: + if fixture is not None: + out, err = fixture.readouterr() + fixture.close() + captured.out = out + captured.err = err + + +@contextmanager +def capturing_logs( + request: SubRequest, +) -> Iterator[CapturedLogs | None]: + logging_plugin: LoggingPlugin | None = request.config.pluginmanager.getplugin( + "logging-plugin" + ) + if logging_plugin is None: + yield None + else: + handler = LogCaptureHandler() + handler.setFormatter(logging_plugin.formatter) + + captured_logs = CapturedLogs(handler) + with catching_logs(handler, level=logging_plugin.log_level): + yield captured_logs + + +@dataclasses.dataclass +class Captured: + out: str = "" + err: str = "" + + +@dataclasses.dataclass +class CapturedLogs: + handler: LogCaptureHandler + + +def pytest_report_to_serializable(report: TestReport) -> dict[str, Any] | None: + if isinstance(report, SubtestReport): + return report._to_json() + return None + + +def pytest_report_from_serializable(data: dict[str, Any]) -> SubtestReport | None: + if data.get("_report_type") == "SubTestReport": + return SubtestReport._from_json(data) + return None + + +# Dict of nodeid -> number of failed subtests. +# Used to fail top-level tests that passed but contain failed subtests. +failed_subtests_key = StashKey[defaultdict[str, int]]() + + +def pytest_configure(config: Config) -> None: + config.stash[failed_subtests_key] = defaultdict(lambda: 0) + + +@hookimpl(tryfirst=True) +def pytest_report_teststatus( + report: TestReport, + config: Config, +) -> tuple[str, str, str | Mapping[str, bool]] | None: + if report.when != "call": + return None + + quiet = config.get_verbosity(Config.VERBOSITY_SUBTESTS) == 0 + if isinstance(report, SubtestReport): + outcome = report.outcome + description = report._sub_test_description() + + if hasattr(report, "wasxfail"): + if quiet: + return "", "", "" + elif outcome == "skipped": + category = "xfailed" + short = "y" # x letter is used for regular xfail, y for subtest xfail + status = "SUBXFAIL" + # outcome == "passed" in an xfail is only possible via a @pytest.mark.xfail mark, which + # is not applicable to a subtest, which only handles pytest.xfail(). + else: # pragma: no cover + # This should not normally happen, unless some plugin is setting wasxfail without + # the correct outcome. Pytest expects the call outcome to be either skipped or + # passed in case of xfail. + # Let's pass this report to the next hook. + return None + return category, short, f"{status}{description}" + + if report.failed: + return outcome, "u", f"SUBFAILED{description}" + else: + if report.passed: + if quiet: + return "", "", "" + else: + return f"subtests {outcome}", "u", f"SUBPASSED{description}" + elif report.skipped: + if quiet: + return "", "", "" + else: + return outcome, "-", f"SUBSKIPPED{description}" + + else: + failed_subtests_count = config.stash[failed_subtests_key][report.nodeid] + # Top-level test, fail if it contains failed subtests and it has passed. + if report.passed and failed_subtests_count > 0: + report.outcome = "failed" + suffix = "s" if failed_subtests_count > 1 else "" + report.longrepr = f"contains {failed_subtests_count} failed subtest{suffix}" + + return None diff --git a/venv/lib/python3.10/site-packages/_pytest/terminal.py b/venv/lib/python3.10/site-packages/_pytest/terminal.py index b0cdb58..e66e4f4 100644 --- a/venv/lib/python3.10/site-packages/_pytest/terminal.py +++ b/venv/lib/python3.10/site-packages/_pytest/terminal.py @@ -1,46 +1,45 @@ +# 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 +from collections.abc import Callable +from collections.abc import Generator +from collections.abc import Mapping +from collections.abc import Sequence import dataclasses import datetime +from functools import partial import inspect +from pathlib import Path import platform import sys import textwrap -import warnings -from collections import Counter -from functools import partial -from pathlib import Path from typing import Any -from typing import Callable -from typing import cast from typing import ClassVar -from typing import Dict -from typing import Generator -from typing import List -from typing import Mapping +from typing import final +from typing import Literal from typing import NamedTuple -from typing import Optional -from typing import Sequence -from typing import Set from typing import TextIO -from typing import Tuple from typing import TYPE_CHECKING -from typing import Union +import warnings import pluggy -import _pytest._version +from _pytest import compat 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 -from _pytest.assertion.util import running_on_ci -from _pytest.compat import final +import _pytest._version +from _pytest.compat import running_on_ci from _pytest.config import _PluggyPlugin from _pytest.config import Config from _pytest.config import ExitCode @@ -54,9 +53,8 @@ from _pytest.reports import BaseReport from _pytest.reports import CollectReport from _pytest.reports import TestReport -if TYPE_CHECKING: - from typing_extensions import Literal +if TYPE_CHECKING: from _pytest.main import Session @@ -71,6 +69,9 @@ KNOWN_TYPES = ( "xpassed", "warnings", "error", + "subtests passed", + "subtests failed", + "subtests skipped", ) _REPORTCHARS_DEFAULT = "fE" @@ -89,7 +90,7 @@ class MoreQuietAction(argparse.Action): dest: str, default: object = None, required: bool = False, - help: Optional[str] = None, + help: str | None = None, ) -> None: super().__init__( option_strings=option_strings, @@ -104,8 +105,8 @@ class MoreQuietAction(argparse.Action): self, parser: argparse.ArgumentParser, namespace: argparse.Namespace, - values: Union[str, Sequence[object], None], - option_string: Optional[str] = None, + 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) @@ -130,12 +131,12 @@ class TestShortLogReport(NamedTuple): category: str letter: str - word: Union[str, Tuple[str, Mapping[str, bool]]] + word: str | tuple[str, Mapping[str, bool]] def pytest_addoption(parser: Parser) -> None: group = parser.getgroup("terminal reporting", "Reporting", after="general") - group._addoption( + group._addoption( # private to use reserved lower-case short option "-v", "--verbose", action="count", @@ -143,21 +144,35 @@ def pytest_addoption(parser: Parser) -> None: dest="verbose", help="Increase verbosity", ) - group._addoption( + group.addoption( "--no-header", action="store_true", default=False, dest="no_header", help="Disable header", ) - group._addoption( + group.addoption( "--no-summary", action="store_true", default=False, dest="no_summary", help="Disable summary", ) - group._addoption( + group.addoption( + "--no-fold-skipped", + action="store_false", + dest="fold_skipped", + default=True, + help="Do not fold skipped tests in short summary.", + ) + group.addoption( + "--force-short-summary", + action="store_true", + dest="force_short_summary", + default=False, + help="Force condensed summary output regardless of verbosity level.", + ) + group._addoption( # private to use reserved lower-case short option "-q", "--quiet", action=MoreQuietAction, @@ -165,14 +180,14 @@ def pytest_addoption(parser: Parser) -> None: dest="verbose", help="Decrease verbosity", ) - group._addoption( + group.addoption( "--verbosity", dest="verbose", type=int, default=0, help="Set verbosity. Default: 0.", ) - group._addoption( + group._addoption( # private to use reserved lower-case short option "-r", action="store", dest="reportchars", @@ -184,7 +199,7 @@ def pytest_addoption(parser: Parser) -> None: "(w)arnings are enabled by default (see --disable-warnings), " "'N' can be used to reset the list. (default: 'fE').", ) - group._addoption( + group.addoption( "--disable-warnings", "--disable-pytest-warnings", default=False, @@ -192,7 +207,7 @@ def pytest_addoption(parser: Parser) -> None: action="store_true", help="Disable warnings summary", ) - group._addoption( + group._addoption( # private to use reserved lower-case short option "-l", "--showlocals", action="store_true", @@ -200,13 +215,13 @@ def pytest_addoption(parser: Parser) -> None: default=False, help="Show locals in tracebacks (disabled by default)", ) - group._addoption( + group.addoption( "--no-showlocals", action="store_false", dest="showlocals", help="Hide locals in tracebacks (negate --showlocals passed through addopts)", ) - group._addoption( + group.addoption( "--tb", metavar="style", action="store", @@ -215,7 +230,14 @@ def pytest_addoption(parser: Parser) -> None: choices=["auto", "long", "short", "no", "line", "native"], help="Traceback print mode (auto/long/short/line/native/no)", ) - group._addoption( + 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", @@ -224,14 +246,14 @@ def pytest_addoption(parser: Parser) -> None: help="Controls how captured stdout/stderr/log is shown on failed tests. " "Default: all.", ) - group._addoption( + group.addoption( "--fulltrace", "--full-trace", action="store_true", default=False, help="Don't cut any tracebacks (default is to cut)", ) - group._addoption( + group.addoption( "--color", metavar="color", action="store", @@ -240,7 +262,7 @@ def pytest_addoption(parser: Parser) -> None: choices=["yes", "no", "auto"], help="Color terminal output (yes/no/auto)", ) - group._addoption( + group.addoption( "--code-highlight", default="yes", choices=["yes", "no"], @@ -255,6 +277,14 @@ def pytest_addoption(parser: Parser) -> None: "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: @@ -268,6 +298,11 @@ def pytest_configure(config: Config) -> None: config.trace.root.setprocessor("pytest:config", mywriter) + # See terminalprogress.py. + # On Windows it's safe to load by default. + if sys.platform == "win32": + config.pluginmanager.import_plugin("terminalprogress") + def getreportopt(config: Config) -> str: reportchars: str = config.option.reportchars @@ -295,7 +330,7 @@ def getreportopt(config: Config) -> str: @hookimpl(trylast=True) # after _pytest.runner -def pytest_report_teststatus(report: BaseReport) -> Tuple[str, str, str]: +def pytest_report_teststatus(report: BaseReport) -> tuple[str, str, str]: letter = "F" if report.passed: letter = "." @@ -323,12 +358,12 @@ class WarningReport: """ message: str - nodeid: Optional[str] = None - fslocation: Optional[Tuple[str, int]] = None + nodeid: str | None = None + fslocation: tuple[str, int] | None = None count_towards_summary: ClassVar = True - def get_location(self, config: Config) -> Optional[str]: + 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 @@ -341,33 +376,39 @@ class WarningReport: @final class TerminalReporter: - def __init__(self, config: Config, file: Optional[TextIO] = None) -> None: + def __init__(self, config: Config, file: TextIO | None = None) -> None: import _pytest.config self.config = config self._numcollected = 0 - self._session: Optional[Session] = None - self._showfspath: Optional[bool] = None + self._session: Session | None = None + self._showfspath: bool | None = None - self.stats: Dict[str, List[Any]] = {} - self._main_color: Optional[str] = None - self._known_types: Optional[List[str]] = 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: Union[None, Path, str, int] = None + 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() + # isatty should be a method but was wrongly implemented as a boolean. + # We use CallableBool here to support both. + self.isatty = compat.CallableBool(file.isatty()) + self._progress_nodeids_reported: set[str] = set() + self._timing_nodeids_reported: set[str] = set() self._show_progress_info = self._determine_show_progress_info() - self._collect_report_last_write: Optional[float] = None - self._already_displayed_warnings: Optional[int] = None - self._keyboardinterrupt_memo: Optional[ExceptionRepr] = None + self._collect_report_last_write = timing.Instant() + self._already_displayed_warnings: int | None = None + self._keyboardinterrupt_memo: ExceptionRepr | None = None - def _determine_show_progress_info(self) -> "Literal['progress', 'count', False]": + def _determine_show_progress_info( + self, + ) -> Literal["progress", "count", "times", 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 @@ -381,10 +422,12 @@ class TerminalReporter: if self.config.getoption("setupshow", False): return False cfg: str = self.config.getini("console_output_style") - if cfg == "progress" or cfg == "progress-even-when-capture-no": + if cfg in {"progress", "progress-even-when-capture-no"}: return "progress" elif cfg == "count": return "count" + elif cfg == "times": + return "times" else: return False @@ -408,22 +451,30 @@ class TerminalReporter: @property def showfspath(self) -> bool: if self._showfspath is None: - return self.verbosity >= 0 + return self.config.get_verbosity(Config.VERBOSITY_TEST_CASES) >= 0 return self._showfspath @showfspath.setter - def showfspath(self, value: Optional[bool]) -> None: + def showfspath(self, value: bool | None) -> None: self._showfspath = value @property def showlongtestinfo(self) -> bool: - return self.verbosity > 0 + return self.config.get_verbosity(Config.VERBOSITY_TEST_CASES) > 0 + + @property + def reported_progress(self) -> int: + """The amount of items reported in the progress so far. + + :meta private: + """ + return len(self._progress_nodeids_reported) 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, **markup: bool) -> None: + 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: @@ -473,10 +524,13 @@ class TerminalReporter: def write(self, content: str, *, flush: bool = False, **markup: bool) -> None: self._tw.write(content, flush=flush, **markup) + def write_raw(self, content: str, *, flush: bool = False) -> None: + self._tw.write_raw(content, flush=flush) + def flush(self) -> None: self._tw.flush() - def write_line(self, line: Union[str, bytes], **markup: bool) -> None: + def write_line(self, line: str | bytes, **markup: bool) -> None: if not isinstance(line, str): line = str(line, errors="replace") self.ensure_newline() @@ -503,8 +557,8 @@ class TerminalReporter: def write_sep( self, sep: str, - title: Optional[str] = None, - fullwidth: Optional[int] = None, + title: str | None = None, + fullwidth: int | None = None, **markup: bool, ) -> None: self.ensure_newline() @@ -554,12 +608,13 @@ class TerminalReporter: self._add_stats("deselected", items) def pytest_runtest_logstart( - self, nodeid: str, location: Tuple[str, Optional[int], str] + 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, *location) + line = self._locationline(nodeid, fspath, lineno, domain) self.write_ensure_prefix(line, "") self.flush() elif self.showfspath: @@ -582,7 +637,6 @@ class TerminalReporter: if not letter and not word: # Probably passed setup/teardown. return - running_xdist = hasattr(rep, "node") if markup is None: was_xfail = hasattr(report, "wasxfail") if rep.passed and not was_xfail: @@ -595,16 +649,25 @@ class TerminalReporter: markup = {"yellow": True} else: markup = {} - if self.verbosity <= 0: + 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: - self._progress_nodeids_reported.add(rep.nodeid) 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.option.verbose < 2: + if self.config.get_verbosity(Config.VERBOSITY_TEST_CASES) < 2: available_width = ( (self._tw.fullwidth - self._tw.width_of_current_line) - len(" [100%]") @@ -622,7 +685,7 @@ class TerminalReporter: self._write_progress_information_filling_space() else: self.ensure_newline() - self._tw.write("[%s]" % rep.node.gateway.id) + self._tw.write(f"[{rep.node.gateway.id}]") if self._show_progress_info: self._tw.write( self._get_progress_information_message() + " ", cyan=True @@ -637,45 +700,82 @@ class TerminalReporter: @property def _is_last_item(self) -> bool: assert self._session is not None - return len(self._progress_nodeids_reported) == self._session.testscollected + return self.reported_progress == self._session.testscollected - def pytest_runtest_logfinish(self, nodeid: str) -> None: - assert self._session - if self.verbosity <= 0 and self._show_progress_info: - if self._show_progress_info == "count": - num_tests = self._session.testscollected - progress_length = len(f" [{num_tests}/{num_tests}]") - else: - progress_length = len(" [100%]") + @hookimpl(wrapper=True) + def pytest_runtestloop(self) -> Generator[None, object, object]: + result = yield - self._progress_nodeids_reported.add(nodeid) + # 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.reported_progress + ): + self._write_progress_information_filling_space() - if self._is_last_item: - self._write_progress_information_filling_space() - else: - main_color, _ = self._get_main_color() - w = self._width_of_current_line - past_edge = w + progress_length + 1 >= self._screen_width - if past_edge: - msg = self._get_progress_information_message() - self._tw.write(msg + "\n", **{main_color: True}) + 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 = self._progress_nodeids_reported + progress = self.reported_progress counter_format = f"{{:{len(str(collected))}d}}" format_string = f" [{counter_format}/{{}}]" - return format_string.format(len(progress), collected) + return format_string.format(progress, collected) return f" [ {collected} / {collected} ]" - else: - if collected: - return " [{:3d}%]".format( - len(self._progress_nodeids_reported) * 100 // collected + if self._show_progress_info == "times": + if not collected: + return "" + all_reports = ( + self._get_reports_to_display("passed") + + self._get_reports_to_display("xpassed") + + self._get_reports_to_display("failed") + + self._get_reports_to_display("xfailed") + + self._get_reports_to_display("skipped") + + self._get_reports_to_display("error") + + self._get_reports_to_display("") + ) + current_location = all_reports[-1].location[0] + not_reported = [ + r for r in all_reports if r.nodeid not in self._timing_nodeids_reported + ] + tests_in_module = sum( + i.location[0] == current_location for i in self._session.items + ) + tests_completed = sum( + r.when == "setup" + for r in not_reported + if r.location[0] == current_location + ) + last_in_module = tests_completed == tests_in_module + if self.showlongtestinfo or last_in_module: + self._timing_nodeids_reported.update(r.nodeid for r in not_reported) + return format_node_duration( + sum(r.duration for r in not_reported if isinstance(r, TestReport)) ) - return " [100%]" + return "" + if collected: + return f" [{self.reported_progress * 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}]") + elif self._show_progress_info == "times": + progress_length = len(" 99h 59m") + 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() @@ -690,10 +790,9 @@ class TerminalReporter: return self._tw.width_of_current_line def pytest_collection(self) -> None: - if self.isatty: + 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) @@ -704,7 +803,7 @@ class TerminalReporter: self._add_stats("skipped", [report]) items = [x for x in report.result if isinstance(x, Item)] self._numcollected += len(items) - if self.isatty: + if self.isatty(): self.report_collect() def report_collect(self, final: bool = False) -> None: @@ -712,14 +811,13 @@ class TerminalReporter: return if not final: - # Only write "collecting" report every 0.5s. - t = timing.time() + # Only write the "collecting" report every `REPORT_COLLECTING_RESOLUTION`. if ( - self._collect_report_last_write is not None - and self._collect_report_last_write > t - REPORT_COLLECTING_RESOLUTION + self._collect_report_last_write.elapsed().seconds + < REPORT_COLLECTING_RESOLUTION ): return - self._collect_report_last_write = t + self._collect_report_last_write = timing.Instant() errors = len(self.stats.get("error", [])) skipped = len(self.stats.get("skipped", [])) @@ -730,14 +828,14 @@ class TerminalReporter: str(self._numcollected) + " item" + ("" if self._numcollected == 1 else "s") ) if errors: - line += " / %d error%s" % (errors, "s" if errors != 1 else "") + line += f" / {errors} error{'s' if errors != 1 else ''}" if deselected: - line += " / %d deselected" % deselected + line += f" / {deselected} deselected" if skipped: - line += " / %d skipped" % skipped + line += f" / {skipped} skipped" if self._numcollected > selected: - line += " / %d selected" % selected - if self.isatty: + line += f" / {selected} selected" + if self.isatty(): self.rewrite(line, bold=True, erase=True) if final: self.write("\n") @@ -745,9 +843,9 @@ class TerminalReporter: self.write_line(line) @hookimpl(trylast=True) - def pytest_sessionstart(self, session: "Session") -> None: + def pytest_sessionstart(self, session: Session) -> None: self._session = session - self._sessionstarttime = timing.time() + self._session_start = timing.Instant() if not self.showheader: return self.write_sep("=", "test session starts", bold=True) @@ -758,9 +856,7 @@ class TerminalReporter: if pypy_version_info: verinfo = ".".join(map(str, pypy_version_info[:3])) msg += f"[pypy-{verinfo}-{pypy_version_info[3]}]" - msg += ", pytest-{}, pluggy-{}".format( - _pytest._version.version, pluggy.__version__ - ) + msg += f", pytest-{_pytest._version.version}, pluggy-{pluggy.__version__}" if ( self.verbosity > 0 or self.config.option.debug @@ -774,7 +870,7 @@ class TerminalReporter: self._write_report_lines_from_hooks(lines) def _write_report_lines_from_hooks( - self, lines: Sequence[Union[str, Sequence[str]]] + self, lines: Sequence[str | Sequence[str]] ) -> None: for line_or_lines in reversed(lines): if isinstance(line_or_lines, str): @@ -783,22 +879,29 @@ class TerminalReporter: for line in line_or_lines: self.write_line(line) - def pytest_report_header(self, config: Config) -> List[str]: + 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)) + warning = "" + if config._ignored_config_files: + warning = f" (WARNING: ignoring pytest config in {', '.join(config._ignored_config_files)}!)" + result.append( + "configfile: " + bestrelpath(config.rootpath, config.inipath) + warning + ) if config.args_source == Config.ArgsSource.TESTPATHS: - testpaths: List[str] = config.getini("testpaths") + testpaths: list[str] = config.getini("testpaths") result.append("testpaths: {}".format(", ".join(testpaths))) plugininfo = config.pluginmanager.list_plugin_distinfo() if plugininfo: - result.append("plugins: %s" % ", ".join(_plugin_nameversions(plugininfo))) + result.append( + "plugins: {}".format(", ".join(_plugin_nameversions(plugininfo))) + ) return result - def pytest_collection_finish(self, session: "Session") -> None: + def pytest_collection_finish(self, session: Session) -> None: self.report_collect(True) lines = self.config.hook.pytest_report_collectionfinish( @@ -821,16 +924,17 @@ class TerminalReporter: rep.toterminal(self._tw) def _printcollecteditems(self, items: Sequence[Item]) -> None: - if self.config.option.verbose < 0: - if self.config.option.verbose < -1: + 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)) + self._tw.line(f"{name}: {count}") else: for item in items: self._tw.line(item.nodeid) return - stack: List[Node] = [] + stack: list[Node] = [] indent = "" for item in items: needed_collectors = item.listchain()[1:] # strip root node @@ -842,19 +946,18 @@ class TerminalReporter: stack.append(col) indent = (len(stack) - 1) * " " self._tw.line(f"{indent}{col}") - if self.config.option.verbose >= 1: + 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(hookwrapper=True) + @hookimpl(wrapper=True) def pytest_sessionfinish( - self, session: "Session", exitstatus: Union[int, ExitCode] - ): - outcome = yield - outcome.get_result() + self, session: Session, exitstatus: int | ExitCode + ) -> Generator[None]: + result = yield self._tw.line("") summary_exit_codes = ( ExitCode.OK, @@ -875,17 +978,22 @@ class TerminalReporter: elif session.shouldstop: self.write_sep("!", str(session.shouldstop), red=True) self.summary_stats() + return result - @hookimpl(hookwrapper=True) - def pytest_terminal_summary(self) -> Generator[None, None, None]: + @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() - yield - self.short_test_summary() - # Display any extra warnings from teardown here (if any). - self.summary_warnings() + 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) @@ -911,7 +1019,7 @@ class TerminalReporter: ) def _locationline( - self, nodeid: str, fspath: str, lineno: Optional[int], domain: str + self, nodeid: str, fspath: str, lineno: int | None, domain: str ) -> str: def mkrel(nodeid: str) -> str: line = self.config.cwd_relative_nodeid(nodeid) @@ -922,7 +1030,7 @@ class TerminalReporter: line += "[".join(values) return line - # collect_fspath comes from testid which has a "/"-normalized path. + # fspath comes from testid which has a "/"-normalized path. if fspath: res = mkrel(nodeid) if self.verbosity >= 2 and nodeid.split("::")[0] != fspath.replace( @@ -956,7 +1064,7 @@ class TerminalReporter: def summary_warnings(self) -> None: if self.hasopt("w"): - all_warnings: Optional[List[WarningReport]] = self.stats.get("warnings") + all_warnings: list[WarningReport] | None = self.stats.get("warnings") if not all_warnings: return @@ -969,11 +1077,11 @@ class TerminalReporter: if not warning_reports: return - reports_grouped_by_message: Dict[str, List[WarningReport]] = {} + 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: + def collapsed_location_report(reports: list[WarningReport]) -> str: locations = [] for w in reports: location = w.get_location(self.config) @@ -1009,12 +1117,20 @@ class TerminalReporter: ) 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("P"): - reports: List[TestReport] = self.getreports("passed") + if self.hasopt(needed_opt): + reports: list[TestReport] = self.getreports(which_reports) if not reports: return - self.write_sep("=", "PASSES") + self.write_sep("=", sep_title) for rep in reports: if rep.sections: msg = self._getfailureheadline(rep) @@ -1022,7 +1138,7 @@ class TerminalReporter: self._outrep_summary(rep) self._handle_teardown_sections(rep.nodeid) - def _get_teardown_reports(self, nodeid: str) -> List[TestReport]: + def _get_teardown_reports(self, nodeid: str) -> list[TestReport]: reports = self.getreports("") return [ report @@ -1048,25 +1164,43 @@ class TerminalReporter: self._tw.line(content) def summary_failures(self) -> None: - if self.config.option.tbstyle != "no": - reports: List[BaseReport] = self.getreports("failed") - if not reports: - return - self.write_sep("=", "FAILURES") - if self.config.option.tbstyle == "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) + 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._outrep_summary(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") + reports: list[BaseReport] = self.getreports("error") if not reports: return self.write_sep("=", "ERRORS") @@ -1096,7 +1230,7 @@ class TerminalReporter: if self.verbosity < -1: return - session_duration = timing.time() - self._sessionstarttime + session_duration = self._session_start.elapsed() (parts, main_color) = self.build_summary_stats_line() line_parts = [] @@ -1111,7 +1245,7 @@ class TerminalReporter: msg = ", ".join(line_parts) main_markup = {main_color: True} - duration = f" in {format_session_duration(session_duration)}" + duration = f" in {format_session_duration(session_duration.seconds)}" duration_with_markup = self._tw.markup(duration, **main_markup) if display_sep: fullwidth += len(duration_with_markup) - len(duration) @@ -1133,7 +1267,7 @@ class TerminalReporter: if not self.reportchars: return - def show_simple(lines: List[str], *, stat: str) -> None: + def show_simple(lines: list[str], *, stat: str) -> None: failed = self.stats.get(stat, []) if not failed: return @@ -1145,13 +1279,13 @@ class TerminalReporter: ) lines.append(line) - def show_xfailed(lines: List[str]) -> None: + def show_xfailed(lines: list[str]) -> None: xfailed = self.stats.get("xfailed", []) for rep in xfailed: - verbose_word = rep._get_verbose_word(self.config) - markup_word = self._tw.markup( - verbose_word, **{_color_for_type["warnings"]: True} + 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 @@ -1160,38 +1294,64 @@ class TerminalReporter: lines.append(line) - def show_xpassed(lines: List[str]) -> None: + def show_xpassed(lines: list[str]) -> None: xpassed = self.stats.get("xpassed", []) for rep in xpassed: - verbose_word = rep._get_verbose_word(self.config) - markup_word = self._tw.markup( - verbose_word, **{_color_for_type["warnings"]: True} + 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 - lines.append(f"{markup_word} {nodeid} {reason}") + if reason: + line += " - " + str(reason) + lines.append(line) - def show_skipped(lines: List[str]) -> None: - skipped: List[CollectReport] = self.stats.get("skipped", []) + 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 = skipped[0]._get_verbose_word(self.config) - markup_word = self._tw.markup( - verbose_word, **{_color_for_type["warnings"]: True} + 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) - ) + lines.append(f"{markup_word} [{num}] {fspath}:{lineno}: {reason}") else: - lines.append("%s [%d] %s: %s" % (markup_word, num, fspath, reason)) + lines.append(f"{markup_word} [{num}] {fspath}: {reason}") - REPORTCHAR_ACTIONS: Mapping[str, Callable[[List[str]], None]] = { + 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"), @@ -1200,7 +1360,7 @@ class TerminalReporter: "E": partial(show_simple, stat="error"), } - lines: List[str] = [] + lines: list[str] = [] for char in self.reportchars: action = REPORTCHAR_ACTIONS.get(char) if action: # skipping e.g. "P" (passed with output) here. @@ -1211,7 +1371,7 @@ class TerminalReporter: for line in lines: self.write_line(line) - def _get_main_color(self) -> Tuple[str, List[str]]: + 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 @@ -1231,22 +1391,22 @@ class TerminalReporter: return main_color def _set_main_color(self) -> None: - unknown_types: List[str] = [] - for found_type in self.stats.keys(): + 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]: + 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: + the example above it would be:: [ ("12 passed", {"green": True}), @@ -1264,14 +1424,14 @@ class TerminalReporter: else: return self._build_normal_summary_stats_line() - def _get_reports_to_display(self, key: str) -> List[Any]: + 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]: + ) -> tuple[list[tuple[str, dict[str, bool]]], str]: main_color, known_types = self._get_main_color() parts = [] @@ -1281,7 +1441,7 @@ class TerminalReporter: 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)) + parts.append(("%d %s" % pluralize(count, key), markup)) # noqa: UP031 if not parts: parts = [("no tests ran", {_color_for_type_default: True})] @@ -1290,7 +1450,7 @@ class TerminalReporter: def _build_collect_only_summary_stats_line( self, - ) -> Tuple[List[Tuple[str, Dict[str, bool]]], str]: + ) -> tuple[list[tuple[str, dict[str, bool]]], str]: deselected = len(self._get_reports_to_display("deselected")) errors = len(self._get_reports_to_display("error")) @@ -1300,7 +1460,7 @@ class TerminalReporter: elif deselected == 0: main_color = "green" - collected_output = "%d %s collected" % pluralize(self._numcollected, "test") + collected_output = "%d %s collected" % pluralize(self._numcollected, "test") # noqa: UP031 parts = [(collected_output, {main_color: True})] else: all_tests_were_deselected = self._numcollected == deselected @@ -1316,7 +1476,7 @@ class TerminalReporter: if errors: main_color = _color_for_type["error"] - parts += [("%d %s" % pluralize(errors, "error"), {main_color: True})] + parts += [("%d %s" % pluralize(errors, "error"), {main_color: True})] # noqa: UP031 return parts, main_color @@ -1331,7 +1491,7 @@ def _get_node_id_with_markup(tw: TerminalWriter, config: Config, rep: BaseReport return path -def _format_trimmed(format: str, msg: str, available_width: int) -> Optional[str]: +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. @@ -1357,27 +1517,35 @@ def _format_trimmed(format: str, msg: str, available_width: int) -> Optional[str def _get_line_with_reprcrash_message( - config: Config, rep: BaseReport, tw: TerminalWriter, word_markup: Dict[str, bool] + 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 = rep._get_verbose_word(config) - word = tw.markup(verbose_word, **word_markup) + 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) + msg: str | None try: - # Type ignored intentionally -- possible AttributeError expected. - msg = rep.longrepr.reprcrash.message # type: ignore[union-attr] + if isinstance(rep.longrepr, str): + msg = rep.longrepr + else: + # Type ignored intentionally -- possible AttributeError expected. + msg = rep.longrepr.reprcrash.message # type: ignore[union-attr] except AttributeError: pass else: - if not running_on_ci(): + if ( + running_on_ci() or config.option.verbose >= 2 + ) and not config.option.force_short_summary: + msg = f" - {msg}" + else: available_width = tw.fullwidth - line_width msg = _format_trimmed(" - {}", msg, available_width) - else: - msg = f" - {msg}" if msg is not None: line += msg @@ -1387,8 +1555,8 @@ def _get_line_with_reprcrash_message( def _folded_skips( startpath: Path, skipped: Sequence[CollectReport], -) -> List[Tuple[int, str, Optional[int], str]]: - d: Dict[Tuple[str, Optional[int], str], List[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) @@ -1405,11 +1573,11 @@ def _folded_skips( and "skip" in keywords and "pytestmark" not in keywords ): - key: Tuple[str, Optional[int], str] = (fspath, None, reason) + key: tuple[str, int | None, str] = (fspath, None, reason) else: key = (fspath, lineno, reason) d.setdefault(key, []).append(event) - values: List[Tuple[int, str, Optional[int], str]] = [] + values: list[tuple[int, str, int | None, str]] = [] for key, events in d.items(): values.append((len(events), *key)) return values @@ -1420,11 +1588,13 @@ _color_for_type = { "error": "red", "warnings": "yellow", "passed": "green", + "subtests passed": "green", + "subtests failed": "red", } _color_for_type_default = "yellow" -def pluralize(count: int, noun: str) -> Tuple[int, str]: +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 @@ -1437,11 +1607,11 @@ def pluralize(count: int, noun: str) -> Tuple[int, str]: return count, noun + "s" if count != 1 else noun -def _plugin_nameversions(plugininfo) -> List[str]: - values: List[str] = [] +def _plugin_nameversions(plugininfo) -> list[str]: + values: list[str] = [] for plugin, dist in plugininfo: # Gets us name and version! - name = "{dist.project_name}-{dist.version}".format(dist=dist) + name = f"{dist.project_name}-{dist.version}" # Questionable convenience, but it keeps things short. if name.startswith("pytest-"): name = name[7:] @@ -1460,13 +1630,36 @@ def format_session_duration(seconds: float) -> str: return f"{seconds:.2f}s ({dt})" +def format_node_duration(seconds: float) -> str: + """Format the given seconds in a human readable manner to show in the test progress.""" + # The formatting is designed to be compact and readable, with at most 7 characters + # for durations below 100 hours. + if seconds < 0.00001: + return f" {seconds * 1000000:.3f}us" + if seconds < 0.0001: + return f" {seconds * 1000000:.2f}us" + if seconds < 0.001: + return f" {seconds * 1000000:.1f}us" + if seconds < 0.01: + return f" {seconds * 1000:.3f}ms" + if seconds < 0.1: + return f" {seconds * 1000:.2f}ms" + if seconds < 1: + return f" {seconds * 1000:.1f}ms" + if seconds < 60: + return f" {seconds:.3f}s" + if seconds < 3600: + return f" {seconds // 60:.0f}m {seconds % 60:.0f}s" + return f" {seconds // 3600:.0f}h {(seconds % 3600) // 60:.0f}m" + + 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 = cast(str, report.wasxfail) + reason = report.wasxfail if reason.startswith("reason: "): reason = reason[len("reason: ") :] return reason @@ -1479,3 +1672,92 @@ def _get_raw_skip_reason(report: TestReport) -> str: elif reason == "Skipped": reason = "" return reason + + +class TerminalProgressPlugin: + """Terminal progress reporting plugin using OSC 9;4 ANSI sequences. + + Emits OSC 9;4 sequences to indicate test progress to terminal + tabs/windows/etc. + + Not all terminal emulators support this feature. + + Ref: https://conemu.github.io/en/AnsiEscapeCodes.html#ConEmu_specific_OSC + """ + + def __init__(self, tr: TerminalReporter) -> None: + self._tr = tr + self._session: Session | None = None + self._has_failures = False + + def _emit_progress( + self, + state: Literal["remove", "normal", "error", "indeterminate", "paused"], + progress: int | None = None, + ) -> None: + """Emit OSC 9;4 sequence for indicating progress to the terminal. + + :param state: + Progress state to set. + :param progress: + Progress value 0-100. Required for "normal", optional for "error" + and "paused", otherwise ignored. + """ + assert progress is None or 0 <= progress <= 100 + + # OSC 9;4 sequence: ESC ] 9 ; 4 ; state ; progress ST + # ST can be ESC \ or BEL. ESC \ seems better supported. + match state: + case "remove": + sequence = "\x1b]9;4;0;\x1b\\" + case "normal": + assert progress is not None + sequence = f"\x1b]9;4;1;{progress}\x1b\\" + case "error": + if progress is not None: + sequence = f"\x1b]9;4;2;{progress}\x1b\\" + else: + sequence = "\x1b]9;4;2;\x1b\\" + case "indeterminate": + sequence = "\x1b]9;4;3;\x1b\\" + case "paused": + if progress is not None: + sequence = f"\x1b]9;4;4;{progress}\x1b\\" + else: + sequence = "\x1b]9;4;4;\x1b\\" + + self._tr.write_raw(sequence, flush=True) + + @hookimpl + def pytest_sessionstart(self, session: Session) -> None: + self._session = session + # Show indeterminate progress during collection. + self._emit_progress("indeterminate") + + @hookimpl + def pytest_collection_finish(self) -> None: + assert self._session is not None + if self._session.testscollected > 0: + # Switch from indeterminate to 0% progress. + self._emit_progress("normal", 0) + + @hookimpl + def pytest_runtest_logreport(self, report: TestReport) -> None: + if report.failed: + self._has_failures = True + + # Let's consider the "call" phase for progress. + if report.when != "call": + return + + # Calculate and emit progress. + assert self._session is not None + collected = self._session.testscollected + if collected > 0: + reported = self._tr.reported_progress + progress = min(reported * 100 // collected, 100) + self._emit_progress("error" if self._has_failures else "normal", progress) + + @hookimpl + def pytest_sessionfinish(self) -> None: + self._emit_progress("remove") diff --git a/venv/lib/python3.10/site-packages/_pytest/terminalprogress.py b/venv/lib/python3.10/site-packages/_pytest/terminalprogress.py new file mode 100644 index 0000000..287f0d5 --- /dev/null +++ b/venv/lib/python3.10/site-packages/_pytest/terminalprogress.py @@ -0,0 +1,30 @@ +# A plugin to register the TerminalProgressPlugin plugin. +# +# This plugin is not loaded by default due to compatibility issues (#13896), +# but can be enabled in one of these ways: +# - The terminal plugin enables it in a few cases where it's safe, and not +# blocked by the user (using e.g. `-p no:terminalprogress`). +# - The user explicitly requests it, e.g. using `-p terminalprogress`. +# +# In a few years, if it's safe, we can consider enabling it by default. Then, +# this file will become unnecessary and can be inlined into terminal.py. + +from __future__ import annotations + +import os + +from _pytest.config import Config +from _pytest.config import hookimpl +from _pytest.terminal import TerminalProgressPlugin +from _pytest.terminal import TerminalReporter + + +@hookimpl(trylast=True) +def pytest_configure(config: Config) -> None: + reporter: TerminalReporter | None = config.pluginmanager.get_plugin( + "terminalreporter" + ) + + if reporter is not None and reporter.isatty() and os.environ.get("TERM") != "dumb": + plugin = TerminalProgressPlugin(reporter) + config.pluginmanager.register(plugin, name="terminalprogress-plugin") diff --git a/venv/lib/python3.10/site-packages/_pytest/threadexception.py b/venv/lib/python3.10/site-packages/_pytest/threadexception.py index 43341e7..eb57783 100644 --- a/venv/lib/python3.10/site-packages/_pytest/threadexception.py +++ b/venv/lib/python3.10/site-packages/_pytest/threadexception.py @@ -1,88 +1,152 @@ +from __future__ import annotations + +import collections +from collections.abc import Callable +import functools +import sys import threading import traceback +from typing import NamedTuple +from typing import TYPE_CHECKING import warnings -from types import TracebackType -from typing import Any -from typing import Callable -from typing import Generator -from typing import Optional -from typing import Type +from _pytest.config import Config +from _pytest.nodes import Item +from _pytest.stash import StashKey +from _pytest.tracemalloc import tracemalloc_message import pytest -# Copied from cpython/Lib/test/support/threading_helper.py, with modifications. -class catch_threading_exception: - """Context manager catching threading.Thread exception using - threading.excepthook. +if TYPE_CHECKING: + pass - 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: Optional["threading.ExceptHookArgs"] = None - self._old_hook: Optional[Callable[["threading.ExceptHookArgs"], Any]] = None - - def _hook(self, args: "threading.ExceptHookArgs") -> None: - self.args = args - - def __enter__(self) -> "catch_threading_exception": - self._old_hook = threading.excepthook - threading.excepthook = self._hook - return self - - def __exit__( - self, - exc_type: Optional[Type[BaseException]], - exc_val: Optional[BaseException], - exc_tb: Optional[TracebackType], - ) -> None: - assert self._old_hook is not None - threading.excepthook = self._old_hook - self._old_hook = None - del self.args +if sys.version_info < (3, 11): + from exceptiongroup import ExceptionGroup -def thread_exception_runtest_hook() -> Generator[None, None, None]: - with catch_threading_exception() as cm: - yield - 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, - ) +class ThreadExceptionMeta(NamedTuple): + msg: str + cause_msg: str + exc_value: BaseException | None + + +thread_exceptions: StashKey[collections.deque[ThreadExceptionMeta | BaseException]] = ( + StashKey() +) + + +def collect_thread_exception(config: Config) -> None: + pop_thread_exception = config.stash[thread_exceptions].pop + errors: list[pytest.PytestUnhandledThreadExceptionWarning | RuntimeError] = [] + meta = None + hook_error = None + try: + while True: + try: + meta = pop_thread_exception() + except IndexError: + break + + if isinstance(meta, BaseException): + hook_error = RuntimeError("Failed to process thread exception") + hook_error.__cause__ = meta + errors.append(hook_error) + continue + + msg = meta.msg + try: + warnings.warn(pytest.PytestUnhandledThreadExceptionWarning(msg)) + except pytest.PytestUnhandledThreadExceptionWarning as e: + # This except happens when the warning is treated as an error (e.g. `-Werror`). + if meta.exc_value is not None: + # Exceptions have a better way to show the traceback, but + # warnings do not, so hide the traceback from the msg and + # set the cause so the traceback shows up in the right place. + e.args = (meta.cause_msg,) + e.__cause__ = meta.exc_value + errors.append(e) + + if len(errors) == 1: + raise errors[0] + if errors: + raise ExceptionGroup("multiple thread exception warnings", errors) + finally: + del errors, meta, hook_error + + +def cleanup( + *, config: Config, prev_hook: Callable[[threading.ExceptHookArgs], object] +) -> None: + try: + try: + # We don't join threads here, so exceptions raised from any + # threads still running by the time _threading_atexits joins them + # do not get captured (see #13027). + collect_thread_exception(config) + finally: + threading.excepthook = prev_hook + finally: + del config.stash[thread_exceptions] + + +def thread_exception_hook( + args: threading.ExceptHookArgs, + /, + *, + append: Callable[[ThreadExceptionMeta | BaseException], object], +) -> None: + try: + # we need to compute these strings here as they might change after + # the excepthook finishes and before the metadata object is + # collected by a pytest hook + thread_name = "" if args.thread is None else args.thread.name + summary = f"Exception in thread {thread_name}" + traceback_message = "\n\n" + "".join( + traceback.format_exception( + args.exc_type, + args.exc_value, + args.exc_traceback, ) - warnings.warn(pytest.PytestUnhandledThreadExceptionWarning(msg)) + ) + tracemalloc_tb = "\n" + tracemalloc_message(args.thread) + msg = summary + traceback_message + tracemalloc_tb + cause_msg = summary + tracemalloc_tb + + append( + ThreadExceptionMeta( + # Compute these strings here as they might change later + msg=msg, + cause_msg=cause_msg, + exc_value=args.exc_value, + ) + ) + except BaseException as e: + append(e) + # Raising this will cause the exception to be logged twice, once in our + # collect_thread_exception and once by sys.excepthook + # which is fine - this should never happen anyway and if it does + # it should probably be reported as a pytest bug. + raise -@pytest.hookimpl(hookwrapper=True, trylast=True) -def pytest_runtest_setup() -> Generator[None, None, None]: - yield from thread_exception_runtest_hook() +def pytest_configure(config: Config) -> None: + prev_hook = threading.excepthook + deque: collections.deque[ThreadExceptionMeta | BaseException] = collections.deque() + config.stash[thread_exceptions] = deque + config.add_cleanup(functools.partial(cleanup, config=config, prev_hook=prev_hook)) + threading.excepthook = functools.partial(thread_exception_hook, append=deque.append) -@pytest.hookimpl(hookwrapper=True, tryfirst=True) -def pytest_runtest_call() -> Generator[None, None, None]: - yield from thread_exception_runtest_hook() +@pytest.hookimpl(trylast=True) +def pytest_runtest_setup(item: Item) -> None: + collect_thread_exception(item.config) -@pytest.hookimpl(hookwrapper=True, tryfirst=True) -def pytest_runtest_teardown() -> Generator[None, None, None]: - yield from thread_exception_runtest_hook() +@pytest.hookimpl(trylast=True) +def pytest_runtest_call(item: Item) -> None: + collect_thread_exception(item.config) + + +@pytest.hookimpl(trylast=True) +def pytest_runtest_teardown(item: Item) -> None: + collect_thread_exception(item.config) diff --git a/venv/lib/python3.10/site-packages/_pytest/timing.py b/venv/lib/python3.10/site-packages/_pytest/timing.py index 925163a..51c3db2 100644 --- a/venv/lib/python3.10/site-packages/_pytest/timing.py +++ b/venv/lib/python3.10/site-packages/_pytest/timing.py @@ -5,8 +5,91 @@ pytest runtime information (issue #185). Fixture "mock_timing" also interacts with this module for pytest's own tests. """ + +from __future__ import annotations + +import dataclasses +from datetime import datetime +from datetime import timezone from time import perf_counter from time import sleep from time import time +from typing import TYPE_CHECKING + + +if TYPE_CHECKING: + from pytest import MonkeyPatch + + +@dataclasses.dataclass(frozen=True) +class Instant: + """ + Represents an instant in time, used to both get the timestamp value and to measure + the duration of a time span. + + Inspired by Rust's `std::time::Instant`. + """ + + # Creation time of this instant, using time.time(), to measure actual time. + # Note: using a `lambda` to correctly get the mocked time via `MockTiming`. + time: float = dataclasses.field(default_factory=lambda: time(), init=False) + + # Performance counter tick of the instant, used to measure precise elapsed time. + # Note: using a `lambda` to correctly get the mocked time via `MockTiming`. + perf_count: float = dataclasses.field( + default_factory=lambda: perf_counter(), init=False + ) + + def elapsed(self) -> Duration: + """Measure the duration since `Instant` was created.""" + return Duration(start=self, stop=Instant()) + + def as_utc(self) -> datetime: + """Instant as UTC datetime.""" + return datetime.fromtimestamp(self.time, timezone.utc) + + +@dataclasses.dataclass(frozen=True) +class Duration: + """A span of time as measured by `Instant.elapsed()`.""" + + start: Instant + stop: Instant + + @property + def seconds(self) -> float: + """Elapsed time of the duration in seconds, measured using a performance counter for precise timing.""" + return self.stop.perf_count - self.start.perf_count + + +@dataclasses.dataclass +class MockTiming: + """Mocks _pytest.timing with a known object that can be used to control timing in tests + deterministically. + + pytest itself should always use functions from `_pytest.timing` instead of `time` directly. + + This then allows us more control over time during testing, if testing code also + uses `_pytest.timing` functions. + + Time is static, and only advances through `sleep` calls, thus tests might sleep over large + numbers and obtain accurate time() calls at the end, making tests reliable and instant.""" + + _current_time: float = datetime(2020, 5, 22, 14, 20, 50).timestamp() + + def sleep(self, seconds: float) -> None: + self._current_time += seconds + + def time(self) -> float: + return self._current_time + + def patch(self, monkeypatch: MonkeyPatch) -> None: + # pylint: disable-next=import-self + from _pytest import timing # noqa: PLW0406 + + monkeypatch.setattr(timing, "sleep", self.sleep) + monkeypatch.setattr(timing, "time", self.time) + monkeypatch.setattr(timing, "perf_counter", self.time) + __all__ = ["perf_counter", "sleep", "time"] diff --git a/venv/lib/python3.10/site-packages/_pytest/tmpdir.py b/venv/lib/python3.10/site-packages/_pytest/tmpdir.py index 3cc2bac..855ad27 100644 --- a/venv/lib/python3.10/site-packages/_pytest/tmpdir.py +++ b/venv/lib/python3.10/site-packages/_pytest/tmpdir.py @@ -1,68 +1,63 @@ +# mypy: allow-untyped-defs """Support for providing temporary directories to test functions.""" + +from __future__ import annotations + +from collections.abc import Generator import dataclasses import os -import re -import tempfile from pathlib import Path +import re from shutil import rmtree +import tempfile from typing import Any -from typing import Dict -from typing import Generator -from typing import Optional -from typing import TYPE_CHECKING -from typing import Union - -from _pytest.nodes import Item -from _pytest.reports import CollectReport -from _pytest.stash import StashKey - -if TYPE_CHECKING: - from typing_extensions import Literal - - RetentionType = Literal["all", "failed", "none"] - - -from _pytest.config.argparsing import Parser +from typing import final +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 .pathlib import cleanup_dead_symlinks -from _pytest.compat import final, get_user_id +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]]() + +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. - - The base directory can be configured using the ``--basetemp`` option. + """Factory for temporary directories under the common base temp directory, + as discussed at :ref:`temporary directory location and retention`. """ - _given_basetemp: Optional[Path] + _given_basetemp: Path | None # pluggy TagTracerSub, not currently exposed, so Any. _trace: Any - _basetemp: Optional[Path] + _basetemp: Path | None _retention_count: int - _retention_policy: "RetentionType" + _retention_policy: RetentionType def __init__( self, - given_basetemp: Optional[Path], + given_basetemp: Path | None, retention_count: int, - retention_policy: "RetentionType", + retention_policy: RetentionType, trace, - basetemp: Optional[Path] = None, + basetemp: Path | None = None, *, _ispytest: bool = False, ) -> None: @@ -85,7 +80,7 @@ class TempPathFactory: config: Config, *, _ispytest: bool = False, - ) -> "TempPathFactory": + ) -> TempPathFactory: """Create a factory according to pytest configuration. :meta private: @@ -201,7 +196,7 @@ class TempPathFactory: return basetemp -def get_user() -> Optional[str]: +def get_user() -> str | None: """Return the current user name, or None if getuser() does not work in the current environment (see #1010).""" try: @@ -209,7 +204,7 @@ def get_user() -> Optional[str]: import getpass return getpass.getuser() - except (ImportError, KeyError): + except (ImportError, OSError, KeyError): return None @@ -230,13 +225,16 @@ 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, + default="3", + # NOTE: Would have been better as an `int` but can't change it now. + type="string", ) 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)", + type="string", default="all", ) @@ -259,26 +257,17 @@ def _mk_tmp(request: FixtureRequest, factory: TempPathFactory) -> Path: @fixture def tmp_path( request: FixtureRequest, tmp_path_factory: TempPathFactory -) -> Generator[Path, None, None]: - """Return a temporary directory path object which is unique to each test - function invocation, created as a sub directory of the base temporary - directory. - - By default, a new base temporary directory is created each test session, - and old bases are removed after 3 sessions, to aid in debugging. - This behavior can be configured with :confval:`tmp_path_retention_count` and - :confval:`tmp_path_retention_policy`. - If ``--basetemp`` is used then it is cleared each session. See :ref:`base - temporary directory`. - - The returned object is a :class:`pathlib.Path` object. +) -> 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] @@ -290,7 +279,7 @@ def tmp_path( del request.node.stash[tmppath_result_key] -def pytest_sessionfinish(session, exitstatus: Union[int, ExitCode]): +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. """ @@ -315,10 +304,12 @@ def pytest_sessionfinish(session, exitstatus: Union[int, ExitCode]): cleanup_dead_symlinks(basetemp) -@hookimpl(tryfirst=True, hookwrapper=True) -def pytest_runtest_makereport(item: Item, call): - outcome = yield - result: CollectReport = outcome.get_result() - - empty: Dict[str, bool] = {} - item.stash.setdefault(tmppath_result_key, empty)[result.when] = result.passed +@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/python3.10/site-packages/_pytest/tracemalloc.py b/venv/lib/python3.10/site-packages/_pytest/tracemalloc.py new file mode 100644 index 0000000..5d0b198 --- /dev/null +++ b/venv/lib/python3.10/site-packages/_pytest/tracemalloc.py @@ -0,0 +1,24 @@ +from __future__ import annotations + + +def tracemalloc_message(source: object) -> str: + if source is None: + return "" + + try: + import tracemalloc + except ImportError: + return "" + + tb = tracemalloc.get_object_traceback(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. + return f"\nObject allocated at:\n{formatted_tb}" + # No need for a leading new line. + url = "https://docs.pytest.org/en/stable/how-to/capture-warnings.html#resource-warnings" + return ( + "Enable tracemalloc to get traceback where the object was allocated.\n" + f"See {url} for more info." + ) diff --git a/venv/lib/python3.10/site-packages/_pytest/unittest.py b/venv/lib/python3.10/site-packages/_pytest/unittest.py index d42a12a..23b9272 100644 --- a/venv/lib/python3.10/site-packages/_pytest/unittest.py +++ b/venv/lib/python3.10/site-packages/_pytest/unittest.py @@ -1,24 +1,29 @@ +# mypy: allow-untyped-defs """Discover and run std-library "unittest" style tests.""" + +from __future__ import annotations + +from collections.abc import Callable +from collections.abc import Generator +from collections.abc import Iterable +from collections.abc import Iterator +from enum import auto +from enum import Enum +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 List -from typing import Optional -from typing import Tuple -from typing import Type from typing import TYPE_CHECKING -from typing import Union +from unittest import TestCase import _pytest._code -import pytest -from _pytest.compat import getimfunc +from _pytest._code import ExceptionInfo +from _pytest.compat import assert_never from _pytest.compat import is_async_function from _pytest.config import hookimpl from _pytest.fixtures import FixtureRequest +from _pytest.monkeypatch import MonkeyPatch from _pytest.nodes import Collector from _pytest.nodes import Item from _pytest.outcomes import exit @@ -29,32 +34,45 @@ from _pytest.python import Class from _pytest.python import Function from _pytest.python import Module from _pytest.runner import CallInfo -from _pytest.scope import Scope +from _pytest.runner import check_interactive_exception +from _pytest.subtests import SubtestContext +from _pytest.subtests import SubtestReport + + +if sys.version_info[:2] < (3, 11): + from exceptiongroup import ExceptionGroup if TYPE_CHECKING: + from types import TracebackType import unittest + import twisted.trial.unittest - _SysExcInfoType = Union[ - Tuple[Type[BaseException], BaseException, types.TracebackType], - Tuple[None, None, None], - ] + +_SysExcInfoType = ( + tuple[type[BaseException], BaseException, types.TracebackType] + | tuple[None, None, None] +) def pytest_pycollect_makeitem( - collector: Union[Module, Class], name: str, obj: object -) -> Optional["UnitTestCase"]: - # Has unittest been imported and is obj a subclass of its TestCase? + 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. - item: UnitTestCase = UnitTestCase.from_parent(collector, name=name, obj=obj) - return item + return UnitTestCase.from_parent(collector, name=name, obj=obj) class UnitTestCase(Class): @@ -62,7 +80,15 @@ class UnitTestCase(Class): # to declare that our children do not support funcargs. nofuncargs = True - def collect(self) -> Iterable[Union[Item, Collector]]: + 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 @@ -71,157 +97,160 @@ class UnitTestCase(Class): skipped = _is_skipped(cls) if not skipped: - self._inject_setup_teardown_fixtures(cls) - self._inject_setup_class_fixture() + 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) - self.session._fixturemanager.parsefactories(self, unittest=True) loader = TestLoader() foundsomething = False for name in loader.getTestCaseNames(self.obj): x = getattr(self.obj, name) if not getattr(x, "__test__", True): continue - funcobj = getimfunc(x) - yield TestCaseFunction.from_parent(self, name=name, callobj=funcobj) + 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) - # Type ignored because `ut` is an opaque module. - if ut is None or runtest != ut.TestCase.runTest: # type: ignore + if ut is None or runtest != ut.TestCase.runTest: yield TestCaseFunction.from_parent(self, name="runTest") - def _inject_setup_teardown_fixtures(self, cls: type) -> None: - """Injects a hidden auto-use fixture to invoke setUpClass/setup_method and corresponding - teardown functions (#517).""" - class_fixture = _make_xunit_fixture( - cls, - "setUpClass", - "tearDownClass", - "doClassCleanups", - scope=Scope.Class, - pass_self=False, - ) - if class_fixture: - cls.__pytest_class_setup = class_fixture # type: ignore[attr-defined] + 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) - method_fixture = _make_xunit_fixture( - cls, - "setup_method", - "teardown_method", - None, - scope=Scope.Function, - pass_self=True, - ) - if method_fixture: - cls.__pytest_method_setup = method_fixture # type: ignore[attr-defined] - - -def _make_xunit_fixture( - obj: type, - setup_name: str, - teardown_name: str, - cleanup_name: Optional[str], - scope: Scope, - pass_self: bool, -): - setup = getattr(obj, setup_name, None) - teardown = getattr(obj, teardown_name, None) - if setup is None and teardown is None: - return None - - if cleanup_name: - cleanup = getattr(obj, cleanup_name, lambda *args: None) - else: - - def cleanup(*args): - pass - - @pytest.fixture( - scope=scope.value, - autouse=True, - # Use a unique name to speed up lookup. - name=f"_unittest_{setup_name}_fixture_{obj.__qualname__}", - ) - def fixture(self, request: FixtureRequest) -> Generator[None, None, None]: - if _is_skipped(self): - reason = self.__unittest_skip_why__ - raise pytest.skip.Exception(reason, _use_item_location=True) - if setup is not None: - try: - if pass_self: - setup(self, request.function) - else: - setup() - # unittest does not call the cleanup function for every BaseException, so we - # follow this here. - except Exception: - if pass_self: - cleanup(self) - else: - cleanup() - - raise - yield - try: - if teardown is not None: - if pass_self: - teardown(self, request.function) - else: - teardown() - finally: - if pass_self: - cleanup(self) + 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: - cleanup() + raise ExceptionGroup("Unittest class cleanup errors", exceptions) - return fixture + def unittest_setup_class_fixture( + request: FixtureRequest, + ) -> Generator[None]: + cls = request.cls + if _is_skipped(cls): + reason = cls.__unittest_skip_why__ + raise 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 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: Optional[List[_pytest._code.ExceptionInfo[BaseException]]] = None - _testcase: Optional["unittest.TestCase"] = None + failfast = False + _excinfo: list[_pytest._code.ExceptionInfo[BaseException]] | None = None - def _getobj(self): - assert self.parent is not None - # Unlike a regular Function in a Class, where `item.obj` returns - # a *bound* method (attached to an instance), TestCaseFunction's - # `obj` returns an *unbound* method (not attached to an instance). - # This inconsistency is probably not desirable, but needs some - # consideration before changing. - return getattr(self.parent.obj, self.originalname) # type: ignore[attr-defined] + 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: Optional[Callable[[], None]] = None - assert self.parent is not None - self._testcase = self.parent.obj(self.name) # type: ignore[attr-defined] - self._obj = getattr(self._testcase, self.name) - if hasattr(self, "_request"): - self._request._fillfixtures() + self._explicit_tearDown: Callable[[], None] | None = None + super().setup() + if sys.version_info < (3, 11): + # A cache of the subTest errors and non-subtest skips in self._outcome. + # Compute and cache these lists once, instead of computing them again and again for each subtest (#13965). + self._cached_errors_and_skips: tuple[list[Any], list[Any]] | None = None def teardown(self) -> None: if self._explicit_tearDown is not None: self._explicit_tearDown() self._explicit_tearDown = None - self._testcase = None self._obj = None + del self._instance + super().teardown() - def startTest(self, testcase: "unittest.TestCase") -> None: + 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) + def _addexcinfo(self, rawexcinfo: _SysExcInfoType) -> None: + rawexcinfo = _handle_twisted_exc_info(rawexcinfo) try: - excinfo = _pytest._code.ExceptionInfo[BaseException].from_exc_info(rawexcinfo) # type: ignore[arg-type] + 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 + _ = excinfo.value + _ = excinfo.traceback except TypeError: try: try: @@ -237,7 +266,7 @@ class TestCaseFunction(Function): except BaseException: fail( "ERROR: Unknown Incompatible Exception " - "representation:\n%r" % (rawexcinfo,), + f"representation:\n{rawexcinfo!r}", pytrace=False, ) except KeyboardInterrupt: @@ -247,7 +276,7 @@ class TestCaseFunction(Function): self.__dict__.setdefault("_excinfo", []).append(excinfo) def addError( - self, testcase: "unittest.TestCase", rawexcinfo: "_SysExcInfoType" + self, testcase: unittest.TestCase, rawexcinfo: _SysExcInfoType ) -> None: try: if isinstance(rawexcinfo[1], exit.Exception): @@ -257,20 +286,47 @@ class TestCaseFunction(Function): self._addexcinfo(rawexcinfo) def addFailure( - self, testcase: "unittest.TestCase", rawexcinfo: "_SysExcInfoType" + 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 addSkip( + self, testcase: unittest.TestCase, reason: str, *, handle_subtests: bool = True + ) -> None: + from unittest.case import _SubTest # type: ignore[attr-defined] + + def add_skip() -> None: + try: + raise skip.Exception(reason, _use_item_location=True) + except skip.Exception: + self._addexcinfo(sys.exc_info()) + + if not handle_subtests: + add_skip() + return + + if isinstance(testcase, _SubTest): + add_skip() + if self._excinfo is not None: + exc_info = self._excinfo[-1] + self.addSubTest(testcase.test_case, testcase, exc_info) + else: + # For python < 3.11: the non-subtest skips have to be added by `add_skip` only after all subtest + # failures are processed by `_addSubTest`: `self.instance._outcome` has no attribute + # `skipped/errors` anymore. + # We also need to check if `self.instance._outcome` is `None` (this happens if the test + # class/method is decorated with `unittest.skip`, see pytest-dev/pytest-subtests#173). + if sys.version_info < (3, 11) and self.instance._outcome is not None: + subtest_errors, _ = self._obtain_errors_and_skips() + if len(subtest_errors) == 0: + add_skip() + else: + add_skip() def addExpectedFailure( self, - testcase: "unittest.TestCase", - rawexcinfo: "_SysExcInfoType", + testcase: unittest.TestCase, + rawexcinfo: _SysExcInfoType, reason: str = "", ) -> None: try: @@ -280,8 +336,8 @@ class TestCaseFunction(Function): def addUnexpectedSuccess( self, - testcase: "unittest.TestCase", - reason: Optional["twisted.trial.unittest.Todo"] = None, + testcase: unittest.TestCase, + reason: twisted.trial.unittest.Todo | None = None, ) -> None: msg = "Unexpected success" if reason: @@ -292,26 +348,26 @@ class TestCaseFunction(Function): except fail.Exception: self._addexcinfo(sys.exc_info()) - def addSuccess(self, testcase: "unittest.TestCase") -> None: + def addSuccess(self, testcase: unittest.TestCase) -> None: pass - def stopTest(self, testcase: "unittest.TestCase") -> None: + def stopTest(self, testcase: unittest.TestCase) -> None: pass - def addDuration(self, testcase: "unittest.TestCase", elapsed: float) -> None: + def addDuration(self, testcase: unittest.TestCase, elapsed: float) -> None: pass def runtest(self) -> None: from _pytest.debugging import maybe_wrap_pytest_function_for_tracing - assert self._testcase is not None + 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): - # Type ignored because self acts as the TestResult, but is not actually one. - self._testcase(result=self) # type: ignore[arg-type] + 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 @@ -323,16 +379,16 @@ class TestCaseFunction(Function): 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 = self._testcase.tearDown - setattr(self._testcase, "tearDown", lambda *args: None) + 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(self._testcase, self.name, self.obj) + setattr(testcase, self.name, self.obj) try: - self._testcase(result=self) # type: ignore[arg-type] + testcase(result=self) finally: - delattr(self._testcase, self.name) + delattr(testcase, self.name) def _traceback_filter( self, excinfo: _pytest._code.ExceptionInfo[BaseException] @@ -345,6 +401,84 @@ class TestCaseFunction(Function): ntraceback = traceback return ntraceback + def addSubTest( + self, + test_case: Any, + test: TestCase, + exc_info: ExceptionInfo[BaseException] + | tuple[type[BaseException], BaseException, TracebackType] + | None, + ) -> None: + exception_info: ExceptionInfo[BaseException] | None + match exc_info: + case tuple(): + exception_info = ExceptionInfo(exc_info, _ispytest=True) + case ExceptionInfo() | None: + exception_info = exc_info + case unreachable: + assert_never(unreachable) + + call_info = CallInfo[None]( + None, + exception_info, + start=0, + stop=0, + duration=0, + when="call", + _ispytest=True, + ) + msg = test._message if isinstance(test._message, str) else None # type: ignore[attr-defined] + report = self.ihook.pytest_runtest_makereport(item=self, call=call_info) + sub_report = SubtestReport._new( + report, + SubtestContext(msg=msg, kwargs=dict(test.params)), # type: ignore[attr-defined] + captured_output=None, + captured_logs=None, + ) + self.ihook.pytest_runtest_logreport(report=sub_report) + if check_interactive_exception(call_info, sub_report): + self.ihook.pytest_exception_interact( + node=self, call=call_info, report=sub_report + ) + + # For python < 3.11: add non-subtest skips once all subtest failures are processed by # `_addSubTest`. + if sys.version_info < (3, 11): + subtest_errors, non_subtest_skip = self._obtain_errors_and_skips() + + # Check if we have non-subtest skips: if there are also sub failures, non-subtest skips are not treated in + # `_addSubTest` and have to be added using `add_skip` after all subtest failures are processed. + if len(non_subtest_skip) > 0 and len(subtest_errors) > 0: + # Make sure we have processed the last subtest failure + last_subset_error = subtest_errors[-1] + if exc_info is last_subset_error[-1]: + # Add non-subtest skips (as they could not be treated in `_addSkip`) + for testcase, reason in non_subtest_skip: + self.addSkip(testcase, reason, handle_subtests=False) + + def _obtain_errors_and_skips(self) -> tuple[list[Any], list[Any]]: + """Compute or obtain the cached values for subtest errors and non-subtest skips.""" + from unittest.case import _SubTest # type: ignore[attr-defined] + + assert sys.version_info < (3, 11), ( + "This workaround only should be used in Python 3.10" + ) + if self._cached_errors_and_skips is not None: + return self._cached_errors_and_skips + + subtest_errors = [ + (x, y) + for x, y in self.instance._outcome.errors + if isinstance(x, _SubTest) and y is not None + ] + + non_subtest_skips = [ + (x, y) + for x, y in self.instance._outcome.skipped + if not isinstance(x, _SubTest) + ] + self._cached_errors_and_skips = (subtest_errors, non_subtest_skips) + return subtest_errors, non_subtest_skips + @hookimpl(tryfirst=True) def pytest_runtest_makereport(item: Item, call: CallInfo[None]) -> None: @@ -357,65 +491,138 @@ def pytest_runtest_makereport(item: Item, call: CallInfo[None]) -> None: 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. + # This covers explicit `raise unittest.SkipTest`. unittest = sys.modules.get("unittest") - if ( - unittest - and call.excinfo - and isinstance(call.excinfo.value, unittest.SkipTest) # type: ignore[attr-defined] - ): + 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 - ) + call2 = CallInfo[None].from_call(lambda: skip(str(excinfo.value)), call.when) call.excinfo = call2.excinfo -# Twisted trial support. - - -@hookimpl(hookwrapper=True) -def pytest_runtest_protocol(item: Item) -> Generator[None, None, None]: - if isinstance(item, TestCaseFunction) and "twisted.trial.unittest" in sys.modules: - ut: Any = sys.modules["twisted.python.failure"] - Failure__init__ = ut.Failure.__init__ - check_testcase_implements_trial_reporter() - - 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 - yield - ut.Failure.__init__ = Failure__init__ - else: - yield - - -def check_testcase_implements_trial_reporter(done: List[int] = []) -> None: - if done: - return - from zope.interface import classImplements - from twisted.trial.itrial import IReporter - - classImplements(TestCaseFunction, IReporter) - done.append(1) - - def _is_skipped(obj) -> bool: """Return True if the given object has been marked with @unittest.skip.""" return bool(getattr(obj, "__unittest_skip__", False)) + + +def pytest_configure() -> None: + """Register the TestCaseFunction class as an IReporter if twisted.trial is available.""" + if _get_twisted_version() is not TwistedVersion.NotInstalled: + from twisted.trial.itrial import IReporter + from zope.interface import classImplements + + classImplements(TestCaseFunction, IReporter) + + +class TwistedVersion(Enum): + """ + The Twisted version installed in the environment. + + We have different workarounds in place for different versions of Twisted. + """ + + # Twisted version 24 or prior. + Version24 = auto() + # Twisted version 25 or later. + Version25 = auto() + # Twisted version is not available. + NotInstalled = auto() + + +def _get_twisted_version() -> TwistedVersion: + # We need to check if "twisted.trial.unittest" is specifically present in sys.modules. + # This is because we intend to integrate with Trial only when it's actively running + # the test suite, but not needed when only other Twisted components are in use. + if "twisted.trial.unittest" not in sys.modules: + return TwistedVersion.NotInstalled + + import importlib.metadata + + import packaging.version + + version_str = importlib.metadata.version("twisted") + version = packaging.version.parse(version_str) + if version.major <= 24: + return TwistedVersion.Version24 + else: + return TwistedVersion.Version25 + + +# Name of the attribute in `twisted.python.Failure` instances that stores +# the `sys.exc_info()` tuple. +# See twisted.trial support in `pytest_runtest_protocol`. +TWISTED_RAW_EXCINFO_ATTR = "_twisted_raw_excinfo" + + +@hookimpl(wrapper=True) +def pytest_runtest_protocol(item: Item) -> Iterator[None]: + if _get_twisted_version() is TwistedVersion.Version24: + import twisted.python.failure as ut + + # Monkeypatch `Failure.__init__` to store the raw exception info. + original__init__ = ut.Failure.__init__ + + def store_raw_exception_info( + self, exc_value=None, exc_type=None, exc_tb=None, captureVars=None + ): # pragma: no cover + if exc_value is None: + raw_exc_info = sys.exc_info() + else: + if exc_type is None: + exc_type = type(exc_value) + if exc_tb is None: + exc_tb = sys.exc_info()[2] + raw_exc_info = (exc_type, exc_value, exc_tb) + setattr(self, TWISTED_RAW_EXCINFO_ATTR, tuple(raw_exc_info)) + try: + original__init__( + self, exc_value, exc_type, exc_tb, captureVars=captureVars + ) + except TypeError: # pragma: no cover + original__init__(self, exc_value, exc_type, exc_tb) + + with MonkeyPatch.context() as patcher: + patcher.setattr(ut.Failure, "__init__", store_raw_exception_info) + return (yield) + else: + return (yield) + + +def _handle_twisted_exc_info( + rawexcinfo: _SysExcInfoType | BaseException, +) -> _SysExcInfoType: + """ + Twisted passes a custom Failure instance to `addError()` instead of using `sys.exc_info()`. + Therefore, if `rawexcinfo` is a `Failure` instance, convert it into the equivalent `sys.exc_info()` tuple + as expected by pytest. + """ + twisted_version = _get_twisted_version() + if twisted_version is TwistedVersion.NotInstalled: + # Unfortunately, because we cannot import `twisted.python.failure` at the top of the file + # and use it in the signature, we need to use `type:ignore` here because we cannot narrow + # the type properly in the `if` statement above. + return rawexcinfo # type:ignore[return-value] + elif twisted_version is TwistedVersion.Version24: + # Twisted calls addError() passing its own classes (like `twisted.python.Failure`), which violates + # the `addError()` signature, so we extract the original `sys.exc_info()` tuple which is stored + # in the object. + if hasattr(rawexcinfo, TWISTED_RAW_EXCINFO_ATTR): + saved_exc_info = getattr(rawexcinfo, TWISTED_RAW_EXCINFO_ATTR) + # Delete the attribute from the original object to avoid leaks. + delattr(rawexcinfo, TWISTED_RAW_EXCINFO_ATTR) + return saved_exc_info # type:ignore[no-any-return] + return rawexcinfo # type:ignore[return-value] + elif twisted_version is TwistedVersion.Version25: + if isinstance(rawexcinfo, BaseException): + import twisted.python.failure + + if isinstance(rawexcinfo, twisted.python.failure.Failure): + tb = rawexcinfo.__traceback__ + if tb is None: + tb = sys.exc_info()[2] + return type(rawexcinfo.value), rawexcinfo.value, tb + + return rawexcinfo # type:ignore[return-value] + else: + # Ideally we would use assert_never() here, but it is not available in all Python versions + # we support, plus we do not require `type_extensions` currently. + assert False, f"Unexpected Twisted version: {twisted_version}" diff --git a/venv/lib/python3.10/site-packages/_pytest/unraisableexception.py b/venv/lib/python3.10/site-packages/_pytest/unraisableexception.py index fcb5d82..0faca36 100644 --- a/venv/lib/python3.10/site-packages/_pytest/unraisableexception.py +++ b/venv/lib/python3.10/site-packages/_pytest/unraisableexception.py @@ -1,93 +1,163 @@ +from __future__ import annotations + +import collections +from collections.abc import Callable +import functools +import gc import sys import traceback +from typing import NamedTuple +from typing import TYPE_CHECKING import warnings -from types import TracebackType -from typing import Any -from typing import Callable -from typing import Generator -from typing import Optional -from typing import Type +from _pytest.config import Config +from _pytest.nodes import Item +from _pytest.stash import StashKey +from _pytest.tracemalloc import tracemalloc_message import pytest -# Copied from cpython/Lib/test/support/__init__.py, with modifications. -class catch_unraisable_exception: - """Context manager catching unraisable exception using sys.unraisablehook. +if TYPE_CHECKING: + pass - 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: Optional["sys.UnraisableHookArgs"] = None - self._old_hook: Optional[Callable[["sys.UnraisableHookArgs"], Any]] = 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) -> "catch_unraisable_exception": - self._old_hook = sys.unraisablehook - sys.unraisablehook = self._hook - return self - - def __exit__( - self, - exc_type: Optional[Type[BaseException]], - exc_val: Optional[BaseException], - exc_tb: Optional[TracebackType], - ) -> None: - assert self._old_hook is not None - sys.unraisablehook = self._old_hook - self._old_hook = None - del self.unraisable +if sys.version_info < (3, 11): + from exceptiongroup import ExceptionGroup -def unraisable_exception_runtest_hook() -> Generator[None, None, None]: - with catch_unraisable_exception() as cm: - yield - 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, - ) +# This is a stash item and not a simple constant to allow pytester to override it. +gc_collect_iterations_key = StashKey[int]() + + +def gc_collect_harder(iterations: int) -> None: + for _ in range(iterations): + gc.collect() + + +class UnraisableMeta(NamedTuple): + msg: str + cause_msg: str + exc_value: BaseException | None + + +unraisable_exceptions: StashKey[collections.deque[UnraisableMeta | BaseException]] = ( + StashKey() +) + + +def collect_unraisable(config: Config) -> None: + pop_unraisable = config.stash[unraisable_exceptions].pop + errors: list[pytest.PytestUnraisableExceptionWarning | RuntimeError] = [] + meta = None + hook_error = None + try: + while True: + try: + meta = pop_unraisable() + except IndexError: + break + + if isinstance(meta, BaseException): + hook_error = RuntimeError("Failed to process unraisable exception") + hook_error.__cause__ = meta + errors.append(hook_error) + continue + + msg = meta.msg + try: + warnings.warn(pytest.PytestUnraisableExceptionWarning(msg)) + except pytest.PytestUnraisableExceptionWarning as e: + # This except happens when the warning is treated as an error (e.g. `-Werror`). + if meta.exc_value is not None: + # Exceptions have a better way to show the traceback, but + # warnings do not, so hide the traceback from the msg and + # set the cause so the traceback shows up in the right place. + e.args = (meta.cause_msg,) + e.__cause__ = meta.exc_value + errors.append(e) + + if len(errors) == 1: + raise errors[0] + if errors: + raise ExceptionGroup("multiple unraisable exception warnings", errors) + finally: + del errors, meta, hook_error + + +def cleanup( + *, config: Config, prev_hook: Callable[[sys.UnraisableHookArgs], object] +) -> None: + # A single collection doesn't necessarily collect everything. + # Constant determined experimentally by the Trio project. + gc_collect_iterations = config.stash.get(gc_collect_iterations_key, 5) + try: + try: + gc_collect_harder(gc_collect_iterations) + collect_unraisable(config) + finally: + sys.unraisablehook = prev_hook + finally: + del config.stash[unraisable_exceptions] + + +def unraisable_hook( + unraisable: sys.UnraisableHookArgs, + /, + *, + append: Callable[[UnraisableMeta | BaseException], object], +) -> None: + try: + # we need to compute these strings here as they might change after + # the unraisablehook finishes and before the metadata object is + # collected by a pytest hook + err_msg = ( + "Exception ignored in" if unraisable.err_msg is None else unraisable.err_msg + ) + summary = f"{err_msg}: {unraisable.object!r}" + traceback_message = "\n\n" + "".join( + traceback.format_exception( + unraisable.exc_type, + unraisable.exc_value, + unraisable.exc_traceback, ) - warnings.warn(pytest.PytestUnraisableExceptionWarning(msg)) + ) + tracemalloc_tb = "\n" + tracemalloc_message(unraisable.object) + msg = summary + traceback_message + tracemalloc_tb + cause_msg = summary + tracemalloc_tb + + append( + UnraisableMeta( + msg=msg, + cause_msg=cause_msg, + exc_value=unraisable.exc_value, + ) + ) + except BaseException as e: + append(e) + # Raising this will cause the exception to be logged twice, once in our + # collect_unraisable and once by the unraisablehook calling machinery + # which is fine - this should never happen anyway and if it does + # it should probably be reported as a pytest bug. + raise -@pytest.hookimpl(hookwrapper=True, tryfirst=True) -def pytest_runtest_setup() -> Generator[None, None, None]: - yield from unraisable_exception_runtest_hook() +def pytest_configure(config: Config) -> None: + prev_hook = sys.unraisablehook + deque: collections.deque[UnraisableMeta | BaseException] = collections.deque() + config.stash[unraisable_exceptions] = deque + config.add_cleanup(functools.partial(cleanup, config=config, prev_hook=prev_hook)) + sys.unraisablehook = functools.partial(unraisable_hook, append=deque.append) -@pytest.hookimpl(hookwrapper=True, tryfirst=True) -def pytest_runtest_call() -> Generator[None, None, None]: - yield from unraisable_exception_runtest_hook() +@pytest.hookimpl(trylast=True) +def pytest_runtest_setup(item: Item) -> None: + collect_unraisable(item.config) -@pytest.hookimpl(hookwrapper=True, tryfirst=True) -def pytest_runtest_teardown() -> Generator[None, None, None]: - yield from unraisable_exception_runtest_hook() +@pytest.hookimpl(trylast=True) +def pytest_runtest_call(item: Item) -> None: + collect_unraisable(item.config) + + +@pytest.hookimpl(trylast=True) +def pytest_runtest_teardown(item: Item) -> None: + collect_unraisable(item.config) diff --git a/venv/lib/python3.10/site-packages/_pytest/warning_types.py b/venv/lib/python3.10/site-packages/_pytest/warning_types.py index bd5f418..93071b4 100644 --- a/venv/lib/python3.10/site-packages/_pytest/warning_types.py +++ b/venv/lib/python3.10/site-packages/_pytest/warning_types.py @@ -1,13 +1,13 @@ +from __future__ import annotations + import dataclasses import inspect -import warnings from types import FunctionType from typing import Any +from typing import final from typing import Generic -from typing import Type from typing import TypeVar - -from _pytest.compat import final +import warnings class PytestWarning(UserWarning): @@ -50,14 +50,14 @@ class PytestDeprecationWarning(PytestWarning, DeprecationWarning): __module__ = "pytest" -class PytestRemovedIn8Warning(PytestDeprecationWarning): - """Warning class for features that will be removed in pytest 8.""" +class PytestRemovedIn9Warning(PytestDeprecationWarning): + """Warning class for features that will be removed in pytest 9.""" __module__ = "pytest" -class PytestReturnNotNoneWarning(PytestRemovedIn8Warning): - """Warning emitted when a test function is returning value other than None.""" +class PytestRemovedIn10Warning(PytestDeprecationWarning): + """Warning class for features that will be removed in pytest 10.""" __module__ = "pytest" @@ -73,21 +73,16 @@ class PytestExperimentalApiWarning(PytestWarning, FutureWarning): __module__ = "pytest" @classmethod - def simple(cls, apiname: str) -> "PytestExperimentalApiWarning": - return cls( - "{apiname} is an experimental api that may change over time".format( - apiname=apiname - ) - ) + 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. +class PytestReturnNotNoneWarning(PytestWarning): + """ + Warning emitted when a test function returns a value other than ``None``. - A coroutine was encountered when collecting test functions, but was not - handled by any async-aware plugin. - Coroutine test functions are not natively supported. + See :ref:`return-not-none` for details. """ __module__ = "pytest" @@ -137,7 +132,7 @@ class UnformattedWarning(Generic[_W]): as opposed to a direct message. """ - category: Type["_W"] + category: type[_W] template: str def format(self, **kwargs: Any) -> _W: @@ -145,6 +140,13 @@ class UnformattedWarning(Generic[_W]): return self.category(self.template.format(**kwargs)) +@final +class PytestFDWarning(PytestWarning): + """When the lsof plugin finds leaked fds.""" + + __module__ = "pytest" + + def warn_explicit_for(method: FunctionType, message: PytestWarning) -> None: """ Issue the warning :param:`message` for the definition of the given :param:`method` diff --git a/venv/lib/python3.10/site-packages/_pytest/warnings.py b/venv/lib/python3.10/site-packages/_pytest/warnings.py index 4aaa944..1dbf002 100644 --- a/venv/lib/python3.10/site-packages/_pytest/warnings.py +++ b/venv/lib/python3.10/site-packages/_pytest/warnings.py @@ -1,37 +1,32 @@ -import sys -import warnings -from contextlib import contextmanager -from typing import Generator -from typing import Optional -from typing import TYPE_CHECKING +# mypy: allow-untyped-defs +from __future__ import annotations + +from collections.abc import Generator +from contextlib import contextmanager +from contextlib import ExitStack +import sys +from typing import Literal +import warnings -import pytest 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 - -if TYPE_CHECKING: - from typing_extensions import Literal - - -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 ", - ) +from _pytest.tracemalloc import tracemalloc_message +import pytest @contextmanager def catch_warnings_for_item( config: Config, ihook, - when: "Literal['config', 'collect', 'runtest']", - item: Optional[Item], -) -> Generator[None, None, None]: + when: Literal["config", "collect", "runtest"], + item: Item | None, + *, + record: bool = True, +) -> 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. @@ -40,15 +35,14 @@ def catch_warnings_for_item( """ 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 - + with warnings.catch_warnings(record=record) as log: 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) + warnings.filterwarnings("error", category=pytest.PytestRemovedIn9Warning) + apply_warning_filters(config_filters, cmdline_filters) # apply filters from "filterwarnings" marks @@ -58,91 +52,100 @@ def catch_warnings_for_item( for arg in mark.args: warnings.filterwarnings(*parse_warning_filter(arg, escape=False)) - yield + try: + yield + finally: + if record: + # mypy can't infer that record=True means log is not None; help it. + assert log is not None - for warning_message in log: - ihook.pytest_warning_recorded.call_historic( - kwargs=dict( - warning_message=warning_message, - nodeid=nodeid, - when=when, - location=None, - ) - ) + 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), + return warnings.formatwarning( + str(warning_message.message), 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 + ) + tracemalloc_message(warning_message.source) -@pytest.hookimpl(hookwrapper=True, tryfirst=True) -def pytest_runtest_protocol(item: Item) -> Generator[None, None, None]: +@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 ): - yield + return (yield) -@pytest.hookimpl(hookwrapper=True, tryfirst=True) -def pytest_collection(session: Session) -> Generator[None, None, None]: +@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 ): - yield + return (yield) -@pytest.hookimpl(hookwrapper=True) +@pytest.hookimpl(wrapper=True) def pytest_terminal_summary( terminalreporter: TerminalReporter, -) -> Generator[None, None, None]: +) -> Generator[None]: config = terminalreporter.config with catch_warnings_for_item( config=config, ihook=config.hook, when="config", item=None ): - yield + return (yield) -@pytest.hookimpl(hookwrapper=True) -def pytest_sessionfinish(session: Session) -> Generator[None, None, None]: +@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 ): - yield + return (yield) -@pytest.hookimpl(hookwrapper=True) +@pytest.hookimpl(wrapper=True) def pytest_load_initial_conftests( - early_config: "Config", -) -> Generator[None, None, None]: + early_config: Config, +) -> Generator[None]: with catch_warnings_for_item( config=early_config, ihook=early_config.hook, when="config", item=None ): - yield + return (yield) + + +def pytest_configure(config: Config) -> None: + with ExitStack() as stack: + stack.enter_context( + catch_warnings_for_item( + config=config, + ihook=config.hook, + when="config", + item=None, + # this disables recording because the terminalreporter has + # finished by the time it comes to reporting logged warnings + # from the end of config cleanup. So for now, this is only + # useful for setting a warning filter with an 'error' action. + record=False, + ) + ) + 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 ", + ) + config.add_cleanup(stack.pop_all().close) diff --git a/venv/lib/python3.10/site-packages/async_timeout-5.0.1.dist-info/INSTALLER b/venv/lib/python3.10/site-packages/aiohappyeyeballs-2.6.1.dist-info/INSTALLER similarity index 100% rename from venv/lib/python3.10/site-packages/async_timeout-5.0.1.dist-info/INSTALLER rename to venv/lib/python3.10/site-packages/aiohappyeyeballs-2.6.1.dist-info/INSTALLER diff --git a/venv/lib/python3.10/site-packages/aiohappyeyeballs-2.6.1.dist-info/LICENSE b/venv/lib/python3.10/site-packages/aiohappyeyeballs-2.6.1.dist-info/LICENSE new file mode 100644 index 0000000..f26bcf4 --- /dev/null +++ b/venv/lib/python3.10/site-packages/aiohappyeyeballs-2.6.1.dist-info/LICENSE @@ -0,0 +1,279 @@ +A. HISTORY OF THE SOFTWARE +========================== + +Python was created in the early 1990s by Guido van Rossum at Stichting +Mathematisch Centrum (CWI, see https://www.cwi.nl) in the Netherlands +as a successor of a language called ABC. Guido remains Python's +principal author, although it includes many contributions from others. + +In 1995, Guido continued his work on Python at the Corporation for +National Research Initiatives (CNRI, see https://www.cnri.reston.va.us) +in Reston, Virginia where he released several versions of the +software. + +In May 2000, Guido and the Python core development team moved to +BeOpen.com to form the BeOpen PythonLabs team. In October of the same +year, the PythonLabs team moved to Digital Creations, which became +Zope Corporation. In 2001, the Python Software Foundation (PSF, see +https://www.python.org/psf/) was formed, a non-profit organization +created specifically to own Python-related Intellectual Property. +Zope Corporation was a sponsoring member of the PSF. + +All Python releases are Open Source (see https://opensource.org for +the Open Source Definition). Historically, most, but not all, Python +releases have also been GPL-compatible; the table below summarizes +the various releases. + + Release Derived Year Owner GPL- + from compatible? (1) + + 0.9.0 thru 1.2 1991-1995 CWI yes + 1.3 thru 1.5.2 1.2 1995-1999 CNRI yes + 1.6 1.5.2 2000 CNRI no + 2.0 1.6 2000 BeOpen.com no + 1.6.1 1.6 2001 CNRI yes (2) + 2.1 2.0+1.6.1 2001 PSF no + 2.0.1 2.0+1.6.1 2001 PSF yes + 2.1.1 2.1+2.0.1 2001 PSF yes + 2.1.2 2.1.1 2002 PSF yes + 2.1.3 2.1.2 2002 PSF yes + 2.2 and above 2.1.1 2001-now PSF yes + +Footnotes: + +(1) GPL-compatible doesn't mean that we're distributing Python under + the GPL. All Python licenses, unlike the GPL, let you distribute + a modified version without making your changes open source. The + GPL-compatible licenses make it possible to combine Python with + other software that is released under the GPL; the others don't. + +(2) According to Richard Stallman, 1.6.1 is not GPL-compatible, + because its license has a choice of law clause. According to + CNRI, however, Stallman's lawyer has told CNRI's lawyer that 1.6.1 + is "not incompatible" with the GPL. + +Thanks to the many outside volunteers who have worked under Guido's +direction to make these releases possible. + + +B. TERMS AND CONDITIONS FOR ACCESSING OR OTHERWISE USING PYTHON +=============================================================== + +Python software and documentation are licensed under the +Python Software Foundation License Version 2. + +Starting with Python 3.8.6, examples, recipes, and other code in +the documentation are dual licensed under the PSF License Version 2 +and the Zero-Clause BSD license. + +Some software incorporated into Python is under different licenses. +The licenses are listed with code falling under that license. + + +PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2 +-------------------------------------------- + +1. This LICENSE AGREEMENT is between the Python Software Foundation +("PSF"), and the Individual or Organization ("Licensee") accessing and +otherwise using this software ("Python") in source or binary form and +its associated documentation. + +2. Subject to the terms and conditions of this License Agreement, PSF hereby +grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce, +analyze, test, perform and/or display publicly, prepare derivative works, +distribute, and otherwise use Python alone or in any derivative version, +provided, however, that PSF's License Agreement and PSF's notice of copyright, +i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, +2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023 Python Software Foundation; +All Rights Reserved" are retained in Python alone or in any derivative version +prepared by Licensee. + +3. In the event Licensee prepares a derivative work that is based on +or incorporates Python or any part thereof, and wants to make +the derivative work available to others as provided herein, then +Licensee hereby agrees to include in any such work a brief summary of +the changes made to Python. + +4. PSF is making Python available to Licensee on an "AS IS" +basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND +DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS +FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT +INFRINGE ANY THIRD PARTY RIGHTS. + +5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON +FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS +A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON, +OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. + +6. This License Agreement will automatically terminate upon a material +breach of its terms and conditions. + +7. Nothing in this License Agreement shall be deemed to create any +relationship of agency, partnership, or joint venture between PSF and +Licensee. This License Agreement does not grant permission to use PSF +trademarks or trade name in a trademark sense to endorse or promote +products or services of Licensee, or any third party. + +8. By copying, installing or otherwise using Python, Licensee +agrees to be bound by the terms and conditions of this License +Agreement. + + +BEOPEN.COM LICENSE AGREEMENT FOR PYTHON 2.0 +------------------------------------------- + +BEOPEN PYTHON OPEN SOURCE LICENSE AGREEMENT VERSION 1 + +1. This LICENSE AGREEMENT is between BeOpen.com ("BeOpen"), having an +office at 160 Saratoga Avenue, Santa Clara, CA 95051, and the +Individual or Organization ("Licensee") accessing and otherwise using +this software in source or binary form and its associated +documentation ("the Software"). + +2. Subject to the terms and conditions of this BeOpen Python License +Agreement, BeOpen hereby grants Licensee a non-exclusive, +royalty-free, world-wide license to reproduce, analyze, test, perform +and/or display publicly, prepare derivative works, distribute, and +otherwise use the Software alone or in any derivative version, +provided, however, that the BeOpen Python License is retained in the +Software, alone or in any derivative version prepared by Licensee. + +3. BeOpen is making the Software available to Licensee on an "AS IS" +basis. BEOPEN MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, BEOPEN MAKES NO AND +DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS +FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE WILL NOT +INFRINGE ANY THIRD PARTY RIGHTS. + +4. BEOPEN SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF THE +SOFTWARE FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS +AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THE SOFTWARE, OR ANY +DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. + +5. This License Agreement will automatically terminate upon a material +breach of its terms and conditions. + +6. This License Agreement shall be governed by and interpreted in all +respects by the law of the State of California, excluding conflict of +law provisions. Nothing in this License Agreement shall be deemed to +create any relationship of agency, partnership, or joint venture +between BeOpen and Licensee. This License Agreement does not grant +permission to use BeOpen trademarks or trade names in a trademark +sense to endorse or promote products or services of Licensee, or any +third party. As an exception, the "BeOpen Python" logos available at +http://www.pythonlabs.com/logos.html may be used according to the +permissions granted on that web page. + +7. By copying, installing or otherwise using the software, Licensee +agrees to be bound by the terms and conditions of this License +Agreement. + + +CNRI LICENSE AGREEMENT FOR PYTHON 1.6.1 +--------------------------------------- + +1. This LICENSE AGREEMENT is between the Corporation for National +Research Initiatives, having an office at 1895 Preston White Drive, +Reston, VA 20191 ("CNRI"), and the Individual or Organization +("Licensee") accessing and otherwise using Python 1.6.1 software in +source or binary form and its associated documentation. + +2. Subject to the terms and conditions of this License Agreement, CNRI +hereby grants Licensee a nonexclusive, royalty-free, world-wide +license to reproduce, analyze, test, perform and/or display publicly, +prepare derivative works, distribute, and otherwise use Python 1.6.1 +alone or in any derivative version, provided, however, that CNRI's +License Agreement and CNRI's notice of copyright, i.e., "Copyright (c) +1995-2001 Corporation for National Research Initiatives; All Rights +Reserved" are retained in Python 1.6.1 alone or in any derivative +version prepared by Licensee. Alternately, in lieu of CNRI's License +Agreement, Licensee may substitute the following text (omitting the +quotes): "Python 1.6.1 is made available subject to the terms and +conditions in CNRI's License Agreement. This Agreement together with +Python 1.6.1 may be located on the internet using the following +unique, persistent identifier (known as a handle): 1895.22/1013. This +Agreement may also be obtained from a proxy server on the internet +using the following URL: http://hdl.handle.net/1895.22/1013". + +3. In the event Licensee prepares a derivative work that is based on +or incorporates Python 1.6.1 or any part thereof, and wants to make +the derivative work available to others as provided herein, then +Licensee hereby agrees to include in any such work a brief summary of +the changes made to Python 1.6.1. + +4. CNRI is making Python 1.6.1 available to Licensee on an "AS IS" +basis. CNRI MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, CNRI MAKES NO AND +DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS +FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON 1.6.1 WILL NOT +INFRINGE ANY THIRD PARTY RIGHTS. + +5. CNRI SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON +1.6.1 FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS +A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 1.6.1, +OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. + +6. This License Agreement will automatically terminate upon a material +breach of its terms and conditions. + +7. This License Agreement shall be governed by the federal +intellectual property law of the United States, including without +limitation the federal copyright law, and, to the extent such +U.S. federal law does not apply, by the law of the Commonwealth of +Virginia, excluding Virginia's conflict of law provisions. +Notwithstanding the foregoing, with regard to derivative works based +on Python 1.6.1 that incorporate non-separable material that was +previously distributed under the GNU General Public License (GPL), the +law of the Commonwealth of Virginia shall govern this License +Agreement only as to issues arising under or with respect to +Paragraphs 4, 5, and 7 of this License Agreement. Nothing in this +License Agreement shall be deemed to create any relationship of +agency, partnership, or joint venture between CNRI and Licensee. This +License Agreement does not grant permission to use CNRI trademarks or +trade name in a trademark sense to endorse or promote products or +services of Licensee, or any third party. + +8. By clicking on the "ACCEPT" button where indicated, or by copying, +installing or otherwise using Python 1.6.1, Licensee agrees to be +bound by the terms and conditions of this License Agreement. + + ACCEPT + + +CWI LICENSE AGREEMENT FOR PYTHON 0.9.0 THROUGH 1.2 +-------------------------------------------------- + +Copyright (c) 1991 - 1995, Stichting Mathematisch Centrum Amsterdam, +The Netherlands. All rights reserved. + +Permission to use, copy, modify, and distribute this software and its +documentation for any purpose and without fee is hereby granted, +provided that the above copyright notice appear in all copies and that +both that copyright notice and this permission notice appear in +supporting documentation, and that the name of Stichting Mathematisch +Centrum or CWI not be used in advertising or publicity pertaining to +distribution of the software without specific, written prior +permission. + +STICHTING MATHEMATISCH CENTRUM DISCLAIMS ALL WARRANTIES WITH REGARD TO +THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS, IN NO EVENT SHALL STICHTING MATHEMATISCH CENTRUM BE LIABLE +FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +ZERO-CLAUSE BSD LICENSE FOR CODE IN THE PYTHON DOCUMENTATION +---------------------------------------------------------------------- + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +PERFORMANCE OF THIS SOFTWARE. diff --git a/venv/lib/python3.10/site-packages/aiohappyeyeballs-2.6.1.dist-info/METADATA b/venv/lib/python3.10/site-packages/aiohappyeyeballs-2.6.1.dist-info/METADATA new file mode 100644 index 0000000..c632040 --- /dev/null +++ b/venv/lib/python3.10/site-packages/aiohappyeyeballs-2.6.1.dist-info/METADATA @@ -0,0 +1,123 @@ +Metadata-Version: 2.3 +Name: aiohappyeyeballs +Version: 2.6.1 +Summary: Happy Eyeballs for asyncio +License: PSF-2.0 +Author: J. Nick Koston +Author-email: nick@koston.org +Requires-Python: >=3.9 +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: Natural Language :: English +Classifier: Operating System :: OS Independent +Classifier: Topic :: Software Development :: Libraries +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 +Classifier: License :: OSI Approved :: Python Software Foundation License +Project-URL: Bug Tracker, https://github.com/aio-libs/aiohappyeyeballs/issues +Project-URL: Changelog, https://github.com/aio-libs/aiohappyeyeballs/blob/main/CHANGELOG.md +Project-URL: Documentation, https://aiohappyeyeballs.readthedocs.io +Project-URL: Repository, https://github.com/aio-libs/aiohappyeyeballs +Description-Content-Type: text/markdown + +# aiohappyeyeballs + +

    + + CI Status + + + Documentation Status + + + Test coverage percentage + +

    +

    + + Poetry + + + Ruff + + + pre-commit + +

    +

    + + PyPI Version + + Supported Python versions + License +

    + +--- + +**Documentation**: https://aiohappyeyeballs.readthedocs.io + +**Source Code**: https://github.com/aio-libs/aiohappyeyeballs + +--- + +[Happy Eyeballs](https://en.wikipedia.org/wiki/Happy_Eyeballs) +([RFC 8305](https://www.rfc-editor.org/rfc/rfc8305.html)) + +## Use case + +This library exists to allow connecting with +[Happy Eyeballs](https://en.wikipedia.org/wiki/Happy_Eyeballs) +([RFC 8305](https://www.rfc-editor.org/rfc/rfc8305.html)) +when you +already have a list of addrinfo and not a DNS name. + +The stdlib version of `loop.create_connection()` +will only work when you pass in an unresolved name which +is not a good fit when using DNS caching or resolving +names via another method such as `zeroconf`. + +## Installation + +Install this via pip (or your favourite package manager): + +`pip install aiohappyeyeballs` + +## License + +[aiohappyeyeballs is licensed under the same terms as cpython itself.](https://github.com/python/cpython/blob/main/LICENSE) + +## Example usage + +```python + +addr_infos = await loop.getaddrinfo("example.org", 80) + +socket = await start_connection(addr_infos) +socket = await start_connection(addr_infos, local_addr_infos=local_addr_infos, happy_eyeballs_delay=0.2) + +transport, protocol = await loop.create_connection( + MyProtocol, sock=socket, ...) + +# Remove the first address for each family from addr_info +pop_addr_infos_interleave(addr_info, 1) + +# Remove all matching address from addr_info +remove_addr_infos(addr_info, "dead::beef::") + +# Convert a local_addr to local_addr_infos +local_addr_infos = addr_to_addr_infos(("127.0.0.1",0)) +``` + +## Credits + +This package contains code from cpython and is licensed under the same terms as cpython itself. + +This package was created with +[Copier](https://copier.readthedocs.io/) and the +[browniebroke/pypackage-template](https://github.com/browniebroke/pypackage-template) +project template. + diff --git a/venv/lib/python3.10/site-packages/aiohappyeyeballs-2.6.1.dist-info/RECORD b/venv/lib/python3.10/site-packages/aiohappyeyeballs-2.6.1.dist-info/RECORD new file mode 100644 index 0000000..2049d95 --- /dev/null +++ b/venv/lib/python3.10/site-packages/aiohappyeyeballs-2.6.1.dist-info/RECORD @@ -0,0 +1,16 @@ +aiohappyeyeballs-2.6.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +aiohappyeyeballs-2.6.1.dist-info/LICENSE,sha256=Oy-B_iHRgcSZxZolbI4ZaEVdZonSaaqFNzv7avQdo78,13936 +aiohappyeyeballs-2.6.1.dist-info/METADATA,sha256=NSXlhJwAfi380eEjAo7BQ4P_TVal9xi0qkyZWibMsVM,5915 +aiohappyeyeballs-2.6.1.dist-info/RECORD,, +aiohappyeyeballs-2.6.1.dist-info/WHEEL,sha256=XbeZDeTWKc1w7CSIyre5aMDU_-PohRwTQceYnisIYYY,88 +aiohappyeyeballs/__init__.py,sha256=x7kktHEtaD9quBcWDJPuLeKyjuVAI-Jj14S9B_5hcTs,361 +aiohappyeyeballs/__pycache__/__init__.cpython-310.pyc,, +aiohappyeyeballs/__pycache__/_staggered.cpython-310.pyc,, +aiohappyeyeballs/__pycache__/impl.cpython-310.pyc,, +aiohappyeyeballs/__pycache__/types.cpython-310.pyc,, +aiohappyeyeballs/__pycache__/utils.cpython-310.pyc,, +aiohappyeyeballs/_staggered.py,sha256=edfVowFx-P-ywJjIEF3MdPtEMVODujV6CeMYr65otac,6900 +aiohappyeyeballs/impl.py,sha256=Dlcm2mTJ28ucrGnxkb_fo9CZzLAkOOBizOt7dreBbXE,9681 +aiohappyeyeballs/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +aiohappyeyeballs/types.py,sha256=YZJIAnyoV4Dz0WFtlaf_OyE4EW7Xus1z7aIfNI6tDDQ,425 +aiohappyeyeballs/utils.py,sha256=on9GxIR0LhEfZu8P6Twi9hepX9zDanuZM20MWsb3xlQ,3028 diff --git a/venv/lib/python3.10/site-packages/aiohappyeyeballs-2.6.1.dist-info/WHEEL b/venv/lib/python3.10/site-packages/aiohappyeyeballs-2.6.1.dist-info/WHEEL new file mode 100644 index 0000000..0582547 --- /dev/null +++ b/venv/lib/python3.10/site-packages/aiohappyeyeballs-2.6.1.dist-info/WHEEL @@ -0,0 +1,4 @@ +Wheel-Version: 1.0 +Generator: poetry-core 2.1.1 +Root-Is-Purelib: true +Tag: py3-none-any diff --git a/venv/lib/python3.10/site-packages/aiohappyeyeballs/__init__.py b/venv/lib/python3.10/site-packages/aiohappyeyeballs/__init__.py new file mode 100644 index 0000000..71c689c --- /dev/null +++ b/venv/lib/python3.10/site-packages/aiohappyeyeballs/__init__.py @@ -0,0 +1,14 @@ +__version__ = "2.6.1" + +from .impl import start_connection +from .types import AddrInfoType, SocketFactoryType +from .utils import addr_to_addr_infos, pop_addr_infos_interleave, remove_addr_infos + +__all__ = ( + "AddrInfoType", + "SocketFactoryType", + "addr_to_addr_infos", + "pop_addr_infos_interleave", + "remove_addr_infos", + "start_connection", +) diff --git a/venv/lib/python3.10/site-packages/aiohappyeyeballs/__pycache__/__init__.cpython-310.pyc b/venv/lib/python3.10/site-packages/aiohappyeyeballs/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..614e56680cb2cea55b5d1a2311eddd6a7a12d483 GIT binary patch literal 486 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 K)5mkwfBhSa>L7>! literal 0 HcmV?d00001 diff --git a/venv/lib/python3.10/site-packages/aiohappyeyeballs/__pycache__/impl.cpython-310.pyc b/venv/lib/python3.10/site-packages/aiohappyeyeballs/__pycache__/impl.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..efc73f66b751abd9ef9baead1efdec2d394fd61a GIT binary patch literal 6336 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 JeOuI?|1V=ZV%Y!y literal 0 HcmV?d00001 diff --git a/venv/lib/python3.10/site-packages/aiohappyeyeballs/__pycache__/types.cpython-310.pyc b/venv/lib/python3.10/site-packages/aiohappyeyeballs/__pycache__/types.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ac621727a3bbc49cefb675475136a83b778124ff GIT binary patch literal 458 zcmZ8e%}N6?5Kgk$b*)=L=)vN}YY#SxR}qU?D1sODl1oXOX&d(^OR`ngH}YM4f?PfM z3LX^)(vr5U$O&1)-{*5=#QDZ(?6Wz7+#C@VkT<1+W1kCKR3G!!WR;0o;SA@+1W43S zQZU~VH8J_vY@Go+$5v3+$imPb&VXKAr}w8|ZYV+TPoJJipMZ-qVhZfdoY?wbt@;ep zVg`223bsY+XZNq%%Y!h;2d>|XJ9&`And>LY^@619rRhKpq5LzE(F&z8rM& zl7ACqd1wNS&6oMIqE`>?hlURF>QWuC$cQdn)dr$mp|S zU_x7hmi+{zfC%=8vIO12F?isBLJvMaV1gI4PY#U2Pz!<`aA;-@Qjh3?RanB@Gu1_q zwLsPc-U9wQ@D<=;XTb_ym_9!sIla#gDsctpe5QHhpi)#|oo zxGz=ida;YwkXh#Dcw;wDT+a=`8E)e1rFH-p9xi5$HHG(76(BF@?L!Xzp@D<}DM{Tb zjZns=l_i}$3I1HX+g@E;*#OlVz=KA(}dwXf4ZmTLP z;oSrx!P7+LWhKbkLE2v^Z9hyN;z7zv)=5$sh|;a~-Mmi{Gwb49j>mCo3VX|fyf!G$Ow*EQAe>g^3!!fhyJ5+suS9DHymrfApQiam% z%MLnm^jR=C!B7rN09YxY0sxL|zzE$%%#daB{RO}Q*H|(OvtWIy-V+Ak!n{L}&XI!d z5p}6Bo|1w;qg2nDnCQmor^M^8#f(ZQ&){P_~&%P7jVi9u{>|7Yta2Su78GA zz%kP}p2Ql6_X4~!HB&IatATiZzze3_g1v#!cd_Q@kQGoXO|4@JbeJ%s^*lm7pO5Le z40@LTMbB-m$AGM%zrg$XtMQ2bNbBbQ6AhM4jf(L~`4;?-!5dxa_PwwNNQde=NP`Z% zJOho&;iGZ9n)uod*38DP%u^{<4B2qCJ_PIG5qxX=_HoSI{c~=d`V(?qI=lDEk3RU=0f)v+49JGt*Sv1kpNOtDh|^?Y2ld?Y6oKQ)&iCX`#2` zU4 None: + """Set the result of a future if it is not already done.""" + if not wait_next.done(): + wait_next.set_result(None) + + +async def _wait_one( + futures: "Iterable[asyncio.Future[Any]]", + loop: asyncio.AbstractEventLoop, +) -> _T: + """Wait for the first future to complete.""" + wait_next = loop.create_future() + + def _on_completion(fut: "asyncio.Future[Any]") -> None: + if not wait_next.done(): + wait_next.set_result(fut) + + for f in futures: + f.add_done_callback(_on_completion) + + try: + return await wait_next + finally: + for f in futures: + f.remove_done_callback(_on_completion) + + +async def staggered_race( + coro_fns: Iterable[Callable[[], Awaitable[_T]]], + delay: Optional[float], + *, + loop: Optional[asyncio.AbstractEventLoop] = None, +) -> Tuple[Optional[_T], Optional[int], List[Optional[BaseException]]]: + """ + Run coroutines with staggered start times and take the first to finish. + + This method takes an iterable of coroutine functions. The first one is + started immediately. From then on, whenever the immediately preceding one + fails (raises an exception), or when *delay* seconds has passed, the next + coroutine is started. This continues until one of the coroutines complete + successfully, in which case all others are cancelled, or until all + coroutines fail. + + The coroutines provided should be well-behaved in the following way: + + * They should only ``return`` if completed successfully. + + * They should always raise an exception if they did not complete + successfully. In particular, if they handle cancellation, they should + probably reraise, like this:: + + try: + # do work + except asyncio.CancelledError: + # undo partially completed work + raise + + Args: + ---- + coro_fns: an iterable of coroutine functions, i.e. callables that + return a coroutine object when called. Use ``functools.partial`` or + lambdas to pass arguments. + + delay: amount of time, in seconds, between starting coroutines. If + ``None``, the coroutines will run sequentially. + + loop: the event loop to use. If ``None``, the running loop is used. + + Returns: + ------- + tuple *(winner_result, winner_index, exceptions)* where + + - *winner_result*: the result of the winning coroutine, or ``None`` + if no coroutines won. + + - *winner_index*: the index of the winning coroutine in + ``coro_fns``, or ``None`` if no coroutines won. If the winning + coroutine may return None on success, *winner_index* can be used + to definitively determine whether any coroutine won. + + - *exceptions*: list of exceptions returned by the coroutines. + ``len(exceptions)`` is equal to the number of coroutines actually + started, and the order is the same as in ``coro_fns``. The winning + coroutine's entry is ``None``. + + """ + loop = loop or asyncio.get_running_loop() + exceptions: List[Optional[BaseException]] = [] + tasks: Set[asyncio.Task[Optional[Tuple[_T, int]]]] = set() + + async def run_one_coro( + coro_fn: Callable[[], Awaitable[_T]], + this_index: int, + start_next: "asyncio.Future[None]", + ) -> Optional[Tuple[_T, int]]: + """ + Run a single coroutine. + + If the coroutine fails, set the exception in the exceptions list and + start the next coroutine by setting the result of the start_next. + + If the coroutine succeeds, return the result and the index of the + coroutine in the coro_fns list. + + If SystemExit or KeyboardInterrupt is raised, re-raise it. + """ + try: + result = await coro_fn() + except RE_RAISE_EXCEPTIONS: + raise + except BaseException as e: + exceptions[this_index] = e + _set_result(start_next) # Kickstart the next coroutine + return None + + return result, this_index + + start_next_timer: Optional[asyncio.TimerHandle] = None + start_next: Optional[asyncio.Future[None]] + task: asyncio.Task[Optional[Tuple[_T, int]]] + done: Union[asyncio.Future[None], asyncio.Task[Optional[Tuple[_T, int]]]] + coro_iter = iter(coro_fns) + this_index = -1 + try: + while True: + if coro_fn := next(coro_iter, None): + this_index += 1 + exceptions.append(None) + start_next = loop.create_future() + task = loop.create_task(run_one_coro(coro_fn, this_index, start_next)) + tasks.add(task) + start_next_timer = ( + loop.call_later(delay, _set_result, start_next) if delay else None + ) + elif not tasks: + # We exhausted the coro_fns list and no tasks are running + # so we have no winner and all coroutines failed. + break + + while tasks or start_next: + done = await _wait_one( + (*tasks, start_next) if start_next else tasks, loop + ) + if done is start_next: + # The current task has failed or the timer has expired + # so we need to start the next task. + start_next = None + if start_next_timer: + start_next_timer.cancel() + start_next_timer = None + + # Break out of the task waiting loop to start the next + # task. + break + + if TYPE_CHECKING: + assert isinstance(done, asyncio.Task) + + tasks.remove(done) + if winner := done.result(): + return *winner, exceptions + finally: + # We either have: + # - a winner + # - all tasks failed + # - a KeyboardInterrupt or SystemExit. + + # + # If the timer is still running, cancel it. + # + if start_next_timer: + start_next_timer.cancel() + + # + # If there are any tasks left, cancel them and than + # wait them so they fill the exceptions list. + # + for task in tasks: + task.cancel() + with contextlib.suppress(asyncio.CancelledError): + await task + + return None, None, exceptions diff --git a/venv/lib/python3.10/site-packages/aiohappyeyeballs/impl.py b/venv/lib/python3.10/site-packages/aiohappyeyeballs/impl.py new file mode 100644 index 0000000..8f3919a --- /dev/null +++ b/venv/lib/python3.10/site-packages/aiohappyeyeballs/impl.py @@ -0,0 +1,259 @@ +"""Base implementation.""" + +import asyncio +import collections +import contextlib +import functools +import itertools +import socket +from typing import List, Optional, Sequence, Set, Union + +from . import _staggered +from .types import AddrInfoType, SocketFactoryType + + +async def start_connection( + addr_infos: Sequence[AddrInfoType], + *, + local_addr_infos: Optional[Sequence[AddrInfoType]] = None, + happy_eyeballs_delay: Optional[float] = None, + interleave: Optional[int] = None, + loop: Optional[asyncio.AbstractEventLoop] = None, + socket_factory: Optional[SocketFactoryType] = None, +) -> socket.socket: + """ + Connect to a TCP server. + + Create a socket connection to a specified destination. The + destination is specified as a list of AddrInfoType tuples as + returned from getaddrinfo(). + + The arguments are, in order: + + * ``family``: the address family, e.g. ``socket.AF_INET`` or + ``socket.AF_INET6``. + * ``type``: the socket type, e.g. ``socket.SOCK_STREAM`` or + ``socket.SOCK_DGRAM``. + * ``proto``: the protocol, e.g. ``socket.IPPROTO_TCP`` or + ``socket.IPPROTO_UDP``. + * ``canonname``: the canonical name of the address, e.g. + ``"www.python.org"``. + * ``sockaddr``: the socket address + + This method is a coroutine which will try to establish the connection + in the background. When successful, the coroutine returns a + socket. + + The expected use case is to use this method in conjunction with + loop.create_connection() to establish a connection to a server:: + + socket = await start_connection(addr_infos) + transport, protocol = await loop.create_connection( + MyProtocol, sock=socket, ...) + """ + if not (current_loop := loop): + current_loop = asyncio.get_running_loop() + + single_addr_info = len(addr_infos) == 1 + + if happy_eyeballs_delay is not None and interleave is None: + # If using happy eyeballs, default to interleave addresses by family + interleave = 1 + + if interleave and not single_addr_info: + addr_infos = _interleave_addrinfos(addr_infos, interleave) + + sock: Optional[socket.socket] = None + # uvloop can raise RuntimeError instead of OSError + exceptions: List[List[Union[OSError, RuntimeError]]] = [] + if happy_eyeballs_delay is None or single_addr_info: + # not using happy eyeballs + for addrinfo in addr_infos: + try: + sock = await _connect_sock( + current_loop, + exceptions, + addrinfo, + local_addr_infos, + None, + socket_factory, + ) + break + except (RuntimeError, OSError): + continue + else: # using happy eyeballs + open_sockets: Set[socket.socket] = set() + try: + sock, _, _ = await _staggered.staggered_race( + ( + functools.partial( + _connect_sock, + current_loop, + exceptions, + addrinfo, + local_addr_infos, + open_sockets, + socket_factory, + ) + for addrinfo in addr_infos + ), + happy_eyeballs_delay, + ) + finally: + # If we have a winner, staggered_race will + # cancel the other tasks, however there is a + # small race window where any of the other tasks + # can be done before they are cancelled which + # will leave the socket open. To avoid this problem + # we pass a set to _connect_sock to keep track of + # the open sockets and close them here if there + # are any "runner up" sockets. + for s in open_sockets: + if s is not sock: + with contextlib.suppress(OSError): + s.close() + open_sockets = None # type: ignore[assignment] + + if sock is None: + all_exceptions = [exc for sub in exceptions for exc in sub] + try: + first_exception = all_exceptions[0] + if len(all_exceptions) == 1: + raise first_exception + else: + # If they all have the same str(), raise one. + model = str(first_exception) + if all(str(exc) == model for exc in all_exceptions): + raise first_exception + # Raise a combined exception so the user can see all + # the various error messages. + msg = "Multiple exceptions: {}".format( + ", ".join(str(exc) for exc in all_exceptions) + ) + # If the errno is the same for all exceptions, raise + # an OSError with that errno. + if isinstance(first_exception, OSError): + first_errno = first_exception.errno + if all( + isinstance(exc, OSError) and exc.errno == first_errno + for exc in all_exceptions + ): + raise OSError(first_errno, msg) + elif isinstance(first_exception, RuntimeError) and all( + isinstance(exc, RuntimeError) for exc in all_exceptions + ): + raise RuntimeError(msg) + # We have a mix of OSError and RuntimeError + # so we have to pick which one to raise. + # and we raise OSError for compatibility + raise OSError(msg) + finally: + all_exceptions = None # type: ignore[assignment] + exceptions = None # type: ignore[assignment] + + return sock + + +async def _connect_sock( + loop: asyncio.AbstractEventLoop, + exceptions: List[List[Union[OSError, RuntimeError]]], + addr_info: AddrInfoType, + local_addr_infos: Optional[Sequence[AddrInfoType]] = None, + open_sockets: Optional[Set[socket.socket]] = None, + socket_factory: Optional[SocketFactoryType] = None, +) -> socket.socket: + """ + Create, bind and connect one socket. + + If open_sockets is passed, add the socket to the set of open sockets. + Any failure caught here will remove the socket from the set and close it. + + Callers can use this set to close any sockets that are not the winner + of all staggered tasks in the result there are runner up sockets aka + multiple winners. + """ + my_exceptions: List[Union[OSError, RuntimeError]] = [] + exceptions.append(my_exceptions) + family, type_, proto, _, address = addr_info + sock = None + try: + if socket_factory is not None: + sock = socket_factory(addr_info) + else: + sock = socket.socket(family=family, type=type_, proto=proto) + if open_sockets is not None: + open_sockets.add(sock) + sock.setblocking(False) + if local_addr_infos is not None: + for lfamily, _, _, _, laddr in local_addr_infos: + # skip local addresses of different family + if lfamily != family: + continue + try: + sock.bind(laddr) + break + except OSError as exc: + msg = ( + f"error while attempting to bind on " + f"address {laddr!r}: " + f"{(exc.strerror or '').lower()}" + ) + exc = OSError(exc.errno, msg) + my_exceptions.append(exc) + else: # all bind attempts failed + if my_exceptions: + raise my_exceptions.pop() + else: + raise OSError(f"no matching local address with {family=} found") + await loop.sock_connect(sock, address) + return sock + except (RuntimeError, OSError) as exc: + my_exceptions.append(exc) + if sock is not None: + if open_sockets is not None: + open_sockets.remove(sock) + try: + sock.close() + except OSError as e: + my_exceptions.append(e) + raise + raise + except: + if sock is not None: + if open_sockets is not None: + open_sockets.remove(sock) + try: + sock.close() + except OSError as e: + my_exceptions.append(e) + raise + raise + finally: + exceptions = my_exceptions = None # type: ignore[assignment] + + +def _interleave_addrinfos( + addrinfos: Sequence[AddrInfoType], first_address_family_count: int = 1 +) -> List[AddrInfoType]: + """Interleave list of addrinfo tuples by family.""" + # Group addresses by family + addrinfos_by_family: collections.OrderedDict[int, List[AddrInfoType]] = ( + collections.OrderedDict() + ) + for addr in addrinfos: + family = addr[0] + if family not in addrinfos_by_family: + addrinfos_by_family[family] = [] + addrinfos_by_family[family].append(addr) + addrinfos_lists = list(addrinfos_by_family.values()) + + reordered: List[AddrInfoType] = [] + if first_address_family_count > 1: + reordered.extend(addrinfos_lists[0][: first_address_family_count - 1]) + del addrinfos_lists[0][: first_address_family_count - 1] + reordered.extend( + a + for a in itertools.chain.from_iterable(itertools.zip_longest(*addrinfos_lists)) + if a is not None + ) + return reordered diff --git a/venv/lib/python3.10/site-packages/curl_cffi-0.5.10.dist-info/REQUESTED b/venv/lib/python3.10/site-packages/aiohappyeyeballs/py.typed similarity index 100% rename from venv/lib/python3.10/site-packages/curl_cffi-0.5.10.dist-info/REQUESTED rename to venv/lib/python3.10/site-packages/aiohappyeyeballs/py.typed diff --git a/venv/lib/python3.10/site-packages/aiohappyeyeballs/types.py b/venv/lib/python3.10/site-packages/aiohappyeyeballs/types.py new file mode 100644 index 0000000..e8c7507 --- /dev/null +++ b/venv/lib/python3.10/site-packages/aiohappyeyeballs/types.py @@ -0,0 +1,17 @@ +"""Types for aiohappyeyeballs.""" + +import socket + +# PY3.9: Import Callable from typing until we drop Python 3.9 support +# https://github.com/python/cpython/issues/87131 +from typing import Callable, Tuple, Union + +AddrInfoType = Tuple[ + Union[int, socket.AddressFamily], + Union[int, socket.SocketKind], + int, + str, + Tuple, # type: ignore[type-arg] +] + +SocketFactoryType = Callable[[AddrInfoType], socket.socket] diff --git a/venv/lib/python3.10/site-packages/aiohappyeyeballs/utils.py b/venv/lib/python3.10/site-packages/aiohappyeyeballs/utils.py new file mode 100644 index 0000000..ea29adb --- /dev/null +++ b/venv/lib/python3.10/site-packages/aiohappyeyeballs/utils.py @@ -0,0 +1,97 @@ +"""Utility functions for aiohappyeyeballs.""" + +import ipaddress +import socket +from typing import Dict, List, Optional, Tuple, Union + +from .types import AddrInfoType + + +def addr_to_addr_infos( + addr: Optional[ + Union[Tuple[str, int, int, int], Tuple[str, int, int], Tuple[str, int]] + ], +) -> Optional[List[AddrInfoType]]: + """Convert an address tuple to a list of addr_info tuples.""" + if addr is None: + return None + host = addr[0] + port = addr[1] + is_ipv6 = ":" in host + if is_ipv6: + flowinfo = 0 + scopeid = 0 + addr_len = len(addr) + if addr_len >= 4: + scopeid = addr[3] # type: ignore[misc] + if addr_len >= 3: + flowinfo = addr[2] # type: ignore[misc] + addr = (host, port, flowinfo, scopeid) + family = socket.AF_INET6 + else: + addr = (host, port) + family = socket.AF_INET + return [(family, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", addr)] + + +def pop_addr_infos_interleave( + addr_infos: List[AddrInfoType], interleave: Optional[int] = None +) -> None: + """ + Pop addr_info from the list of addr_infos by family up to interleave times. + + The interleave parameter is used to know how many addr_infos for + each family should be popped of the top of the list. + """ + seen: Dict[int, int] = {} + if interleave is None: + interleave = 1 + to_remove: List[AddrInfoType] = [] + for addr_info in addr_infos: + family = addr_info[0] + if family not in seen: + seen[family] = 0 + if seen[family] < interleave: + to_remove.append(addr_info) + seen[family] += 1 + for addr_info in to_remove: + addr_infos.remove(addr_info) + + +def _addr_tuple_to_ip_address( + addr: Union[Tuple[str, int], Tuple[str, int, int, int]], +) -> Union[ + Tuple[ipaddress.IPv4Address, int], Tuple[ipaddress.IPv6Address, int, int, int] +]: + """Convert an address tuple to an IPv4Address.""" + return (ipaddress.ip_address(addr[0]), *addr[1:]) + + +def remove_addr_infos( + addr_infos: List[AddrInfoType], + addr: Union[Tuple[str, int], Tuple[str, int, int, int]], +) -> None: + """ + Remove an address from the list of addr_infos. + + The addr value is typically the return value of + sock.getpeername(). + """ + bad_addrs_infos: List[AddrInfoType] = [] + for addr_info in addr_infos: + if addr_info[-1] == addr: + bad_addrs_infos.append(addr_info) + if bad_addrs_infos: + for bad_addr_info in bad_addrs_infos: + addr_infos.remove(bad_addr_info) + return + # Slow path in case addr is formatted differently + match_addr = _addr_tuple_to_ip_address(addr) + for addr_info in addr_infos: + if match_addr == _addr_tuple_to_ip_address(addr_info[-1]): + bad_addrs_infos.append(addr_info) + if bad_addrs_infos: + for bad_addr_info in bad_addrs_infos: + addr_infos.remove(bad_addr_info) + return + raise ValueError(f"Address {addr} not found in addr_infos") diff --git a/venv/lib/python3.10/site-packages/curl_cffi-0.5.10.dist-info/INSTALLER b/venv/lib/python3.10/site-packages/aiohttp-3.9.1.dist-info/INSTALLER similarity index 100% rename from venv/lib/python3.10/site-packages/curl_cffi-0.5.10.dist-info/INSTALLER rename to venv/lib/python3.10/site-packages/aiohttp-3.9.1.dist-info/INSTALLER diff --git a/venv/lib/python3.10/site-packages/aiohttp-3.9.1.dist-info/LICENSE.txt b/venv/lib/python3.10/site-packages/aiohttp-3.9.1.dist-info/LICENSE.txt new file mode 100644 index 0000000..e497a32 --- /dev/null +++ b/venv/lib/python3.10/site-packages/aiohttp-3.9.1.dist-info/LICENSE.txt @@ -0,0 +1,13 @@ + Copyright aio-libs contributors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/venv/lib/python3.10/site-packages/aiohttp-3.9.1.dist-info/METADATA b/venv/lib/python3.10/site-packages/aiohttp-3.9.1.dist-info/METADATA new file mode 100644 index 0000000..e6d31c1 --- /dev/null +++ b/venv/lib/python3.10/site-packages/aiohttp-3.9.1.dist-info/METADATA @@ -0,0 +1,243 @@ +Metadata-Version: 2.1 +Name: aiohttp +Version: 3.9.1 +Summary: Async http client/server framework (asyncio) +Home-page: https://github.com/aio-libs/aiohttp +Maintainer: aiohttp team +Maintainer-email: team@aiohttp.org +License: Apache 2 +Project-URL: Chat: Matrix, https://matrix.to/#/#aio-libs:matrix.org +Project-URL: Chat: Matrix Space, https://matrix.to/#/#aio-libs-space:matrix.org +Project-URL: CI: GitHub Actions, https://github.com/aio-libs/aiohttp/actions?query=workflow%3ACI +Project-URL: Coverage: codecov, https://codecov.io/github/aio-libs/aiohttp +Project-URL: Docs: Changelog, https://docs.aiohttp.org/en/stable/changes.html +Project-URL: Docs: RTD, https://docs.aiohttp.org +Project-URL: GitHub: issues, https://github.com/aio-libs/aiohttp/issues +Project-URL: GitHub: repo, https://github.com/aio-libs/aiohttp +Classifier: Development Status :: 5 - Production/Stable +Classifier: Framework :: AsyncIO +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: Apache Software License +Classifier: Operating System :: POSIX +Classifier: Operating System :: MacOS :: MacOS X +Classifier: Operating System :: Microsoft :: Windows +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Topic :: Internet :: WWW/HTTP +Requires-Python: >=3.8 +Description-Content-Type: text/x-rst +License-File: LICENSE.txt +Requires-Dist: attrs >=17.3.0 +Requires-Dist: multidict <7.0,>=4.5 +Requires-Dist: yarl <2.0,>=1.0 +Requires-Dist: frozenlist >=1.1.1 +Requires-Dist: aiosignal >=1.1.2 +Requires-Dist: async-timeout <5.0,>=4.0 ; python_version < "3.11" +Provides-Extra: speedups +Requires-Dist: brotlicffi ; (platform_python_implementation != "CPython") and extra == 'speedups' +Requires-Dist: Brotli ; (platform_python_implementation == "CPython") and extra == 'speedups' +Requires-Dist: aiodns ; (sys_platform == "linux" or sys_platform == "darwin") and extra == 'speedups' + +================================== +Async http client/server framework +================================== + +.. image:: https://raw.githubusercontent.com/aio-libs/aiohttp/master/docs/aiohttp-plain.svg + :height: 64px + :width: 64px + :alt: aiohttp logo + +| + +.. image:: https://github.com/aio-libs/aiohttp/workflows/CI/badge.svg + :target: https://github.com/aio-libs/aiohttp/actions?query=workflow%3ACI + :alt: GitHub Actions status for master branch + +.. image:: https://codecov.io/gh/aio-libs/aiohttp/branch/master/graph/badge.svg + :target: https://codecov.io/gh/aio-libs/aiohttp + :alt: codecov.io status for master branch + +.. image:: https://badge.fury.io/py/aiohttp.svg + :target: https://pypi.org/project/aiohttp + :alt: Latest PyPI package version + +.. image:: https://readthedocs.org/projects/aiohttp/badge/?version=latest + :target: https://docs.aiohttp.org/ + :alt: Latest Read The Docs + +.. image:: https://img.shields.io/matrix/aio-libs:matrix.org?label=Discuss%20on%20Matrix%20at%20%23aio-libs%3Amatrix.org&logo=matrix&server_fqdn=matrix.org&style=flat + :target: https://matrix.to/#/%23aio-libs:matrix.org + :alt: Matrix Room — #aio-libs:matrix.org + +.. image:: https://img.shields.io/matrix/aio-libs-space:matrix.org?label=Discuss%20on%20Matrix%20at%20%23aio-libs-space%3Amatrix.org&logo=matrix&server_fqdn=matrix.org&style=flat + :target: https://matrix.to/#/%23aio-libs-space:matrix.org + :alt: Matrix Space — #aio-libs-space:matrix.org + + +Key Features +============ + +- Supports both client and server side of HTTP protocol. +- Supports both client and server Web-Sockets out-of-the-box and avoids + Callback Hell. +- Provides Web-server with middleware and pluggable routing. + + +Getting started +=============== + +Client +------ + +To get something from the web: + +.. code-block:: python + + import aiohttp + import asyncio + + async def main(): + + async with aiohttp.ClientSession() as session: + async with session.get('http://python.org') as response: + + print("Status:", response.status) + print("Content-type:", response.headers['content-type']) + + html = await response.text() + print("Body:", html[:15], "...") + + asyncio.run(main()) + +This prints: + +.. code-block:: + + Status: 200 + Content-type: text/html; charset=utf-8 + Body: ... + +Coming from `requests `_ ? Read `why we need so many lines `_. + +Server +------ + +An example using a simple server: + +.. code-block:: python + + # examples/server_simple.py + from aiohttp import web + + async def handle(request): + name = request.match_info.get('name', "Anonymous") + text = "Hello, " + name + return web.Response(text=text) + + async def wshandle(request): + ws = web.WebSocketResponse() + await ws.prepare(request) + + async for msg in ws: + if msg.type == web.WSMsgType.text: + await ws.send_str("Hello, {}".format(msg.data)) + elif msg.type == web.WSMsgType.binary: + await ws.send_bytes(msg.data) + elif msg.type == web.WSMsgType.close: + break + + return ws + + + app = web.Application() + app.add_routes([web.get('/', handle), + web.get('/echo', wshandle), + web.get('/{name}', handle)]) + + if __name__ == '__main__': + web.run_app(app) + + +Documentation +============= + +https://aiohttp.readthedocs.io/ + + +Demos +===== + +https://github.com/aio-libs/aiohttp-demos + + +External links +============== + +* `Third party libraries + `_ +* `Built with aiohttp + `_ +* `Powered by aiohttp + `_ + +Feel free to make a Pull Request for adding your link to these pages! + + +Communication channels +====================== + +*aio-libs Discussions*: https://github.com/aio-libs/aiohttp/discussions + +*gitter chat* https://gitter.im/aio-libs/Lobby + +We support `Stack Overflow +`_. +Please add *aiohttp* tag to your question there. + +Requirements +============ + +- async-timeout_ +- attrs_ +- multidict_ +- yarl_ +- frozenlist_ + +Optionally you may install the aiodns_ library (highly recommended for sake of speed). + +.. _aiodns: https://pypi.python.org/pypi/aiodns +.. _attrs: https://github.com/python-attrs/attrs +.. _multidict: https://pypi.python.org/pypi/multidict +.. _frozenlist: https://pypi.org/project/frozenlist/ +.. _yarl: https://pypi.python.org/pypi/yarl +.. _async-timeout: https://pypi.python.org/pypi/async_timeout + +License +======= + +``aiohttp`` is offered under the Apache 2 license. + + +Keepsafe +======== + +The aiohttp community would like to thank Keepsafe +(https://www.getkeepsafe.com) for its support in the early days of +the project. + + +Source code +=========== + +The latest developer version is available in a GitHub repository: +https://github.com/aio-libs/aiohttp + +Benchmarks +========== + +If you are interested in efficiency, the AsyncIO community maintains a +list of benchmarks on the official wiki: +https://github.com/python/asyncio/wiki/Benchmarks diff --git a/venv/lib/python3.10/site-packages/aiohttp-3.9.1.dist-info/RECORD b/venv/lib/python3.10/site-packages/aiohttp-3.9.1.dist-info/RECORD new file mode 100644 index 0000000..c5f9511 --- /dev/null +++ b/venv/lib/python3.10/site-packages/aiohttp-3.9.1.dist-info/RECORD @@ -0,0 +1,120 @@ +aiohttp-3.9.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +aiohttp-3.9.1.dist-info/LICENSE.txt,sha256=n4DQ2311WpQdtFchcsJw7L2PCCuiFd3QlZhZQu2Uqes,588 +aiohttp-3.9.1.dist-info/METADATA,sha256=62Q_RgoSLj5AlXdi63xBFYgkT11mgnluERhDIRpWHjY,7357 +aiohttp-3.9.1.dist-info/RECORD,, +aiohttp-3.9.1.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +aiohttp-3.9.1.dist-info/WHEEL,sha256=1FEjxEYgybphwh9S0FO9IcZ0B-NIeM2ko8OzhFZeOeQ,152 +aiohttp-3.9.1.dist-info/top_level.txt,sha256=iv-JIaacmTl-hSho3QmphcKnbRRYx1st47yjz_178Ro,8 +aiohttp/.hash/_cparser.pxd.hash,sha256=hYa9Vje-oMs2eh_7MfCPOh2QW_1x1yCjcZuc7AmwLd0,121 +aiohttp/.hash/_find_header.pxd.hash,sha256=_mbpD6vM-CVCKq3ulUvsOAz5Wdo88wrDzfpOsMQaMNA,125 +aiohttp/.hash/_helpers.pyi.hash,sha256=Ew4BZDc2LqFwszgZZUHHrJvw5P8HBhJ700n1Ntg52hE,121 +aiohttp/.hash/_helpers.pyx.hash,sha256=5JQ6BlMBE4HnRaCGdkK9_wpL3ZSWpU1gyLYva0Wwx2c,121 +aiohttp/.hash/_http_parser.pyx.hash,sha256=IRBIywLdT4-0kqWhb0g0WPjh6Gu10TreFmLI8JQF-L8,125 +aiohttp/.hash/_http_writer.pyx.hash,sha256=3Qg3T3D-Ud73elzPHBufK0yEu9tP5jsu6g-aPKQY9gE,125 +aiohttp/.hash/_websocket.pyx.hash,sha256=M97f-Yti-4vnE4GNTD1s_DzKs-fG_ww3jle6EUvixnE,123 +aiohttp/.hash/hdrs.py.hash,sha256=2oEszMWjYFTHoF2w4OcFCoM7osv4vY9KLLJCu9HP0xI,116 +aiohttp/__init__.py,sha256=EnBN-3iIseCzm7llWOVNSbPpNTarRN1dF2SSgRKtB-g,7782 +aiohttp/__pycache__/__init__.cpython-310.pyc,, +aiohttp/__pycache__/abc.cpython-310.pyc,, +aiohttp/__pycache__/base_protocol.cpython-310.pyc,, +aiohttp/__pycache__/client.cpython-310.pyc,, +aiohttp/__pycache__/client_exceptions.cpython-310.pyc,, +aiohttp/__pycache__/client_proto.cpython-310.pyc,, +aiohttp/__pycache__/client_reqrep.cpython-310.pyc,, +aiohttp/__pycache__/client_ws.cpython-310.pyc,, +aiohttp/__pycache__/compression_utils.cpython-310.pyc,, +aiohttp/__pycache__/connector.cpython-310.pyc,, +aiohttp/__pycache__/cookiejar.cpython-310.pyc,, +aiohttp/__pycache__/formdata.cpython-310.pyc,, +aiohttp/__pycache__/hdrs.cpython-310.pyc,, +aiohttp/__pycache__/helpers.cpython-310.pyc,, +aiohttp/__pycache__/http.cpython-310.pyc,, +aiohttp/__pycache__/http_exceptions.cpython-310.pyc,, +aiohttp/__pycache__/http_parser.cpython-310.pyc,, +aiohttp/__pycache__/http_websocket.cpython-310.pyc,, +aiohttp/__pycache__/http_writer.cpython-310.pyc,, +aiohttp/__pycache__/locks.cpython-310.pyc,, +aiohttp/__pycache__/log.cpython-310.pyc,, +aiohttp/__pycache__/multipart.cpython-310.pyc,, +aiohttp/__pycache__/payload.cpython-310.pyc,, +aiohttp/__pycache__/payload_streamer.cpython-310.pyc,, +aiohttp/__pycache__/pytest_plugin.cpython-310.pyc,, +aiohttp/__pycache__/resolver.cpython-310.pyc,, +aiohttp/__pycache__/streams.cpython-310.pyc,, +aiohttp/__pycache__/tcp_helpers.cpython-310.pyc,, +aiohttp/__pycache__/test_utils.cpython-310.pyc,, +aiohttp/__pycache__/tracing.cpython-310.pyc,, +aiohttp/__pycache__/typedefs.cpython-310.pyc,, +aiohttp/__pycache__/web.cpython-310.pyc,, +aiohttp/__pycache__/web_app.cpython-310.pyc,, +aiohttp/__pycache__/web_exceptions.cpython-310.pyc,, +aiohttp/__pycache__/web_fileresponse.cpython-310.pyc,, +aiohttp/__pycache__/web_log.cpython-310.pyc,, +aiohttp/__pycache__/web_middlewares.cpython-310.pyc,, +aiohttp/__pycache__/web_protocol.cpython-310.pyc,, +aiohttp/__pycache__/web_request.cpython-310.pyc,, +aiohttp/__pycache__/web_response.cpython-310.pyc,, +aiohttp/__pycache__/web_routedef.cpython-310.pyc,, +aiohttp/__pycache__/web_runner.cpython-310.pyc,, +aiohttp/__pycache__/web_server.cpython-310.pyc,, +aiohttp/__pycache__/web_urldispatcher.cpython-310.pyc,, +aiohttp/__pycache__/web_ws.cpython-310.pyc,, +aiohttp/__pycache__/worker.cpython-310.pyc,, +aiohttp/_cparser.pxd,sha256=8jGIg-VJ9p3llwCakUYDsPGxA4HiZe9dmK9Jmtlz-5g,4318 +aiohttp/_find_header.pxd,sha256=0GfwFCPN2zxEKTO1_MA5sYq2UfzsG8kcV3aTqvwlz3g,68 +aiohttp/_headers.pxi,sha256=n701k28dVPjwRnx5j6LpJhLTfj7dqu2vJt7f0O60Oyg,2007 +aiohttp/_helpers.cpython-310-x86_64-linux-gnu.so,sha256=KZA_we6lVYYLcxtV5fNq7f72xOwd6kqp5YzJEJT9wMI,508784 +aiohttp/_helpers.pyi,sha256=ZoKiJSS51PxELhI2cmIr5737YjjZcJt7FbIRO3ym1Ss,202 +aiohttp/_helpers.pyx,sha256=XeLbNft5X_4ifi8QB8i6TyrRuayijMSO3IDHeSA89uM,1049 +aiohttp/_http_parser.cpython-310-x86_64-linux-gnu.so,sha256=nd6pAfvgcssapA1P0utNzCe9yF8otp2D8oJdoByzFlM,2587480 +aiohttp/_http_parser.pyx,sha256=fzKwwVlcGnGVeiGOzo05d-2Rccqtl9-PzYKqgK3fxdI,28058 +aiohttp/_http_writer.cpython-310-x86_64-linux-gnu.so,sha256=llvQZrZI6yQIHp0lP-dgdlOWnLIIPLLWNhgEi9RsDI0,459368 +aiohttp/_http_writer.pyx,sha256=aIHAp8g4ZV5kbGRdmZce-vXjELw2M6fGKyJuOdgYQqw,4575 +aiohttp/_websocket.cpython-310-x86_64-linux-gnu.so,sha256=xO73eZVpxho_Omoz3Bh9muMnRL-7qPGEL5BTAyOTEeQ,234032 +aiohttp/_websocket.pyx,sha256=1XuOSNDCbyDrzF5uMA2isqausSs8l2jWTLDlNDLM9Io,1561 +aiohttp/abc.py,sha256=nAyCo7BadpvvExxO1khNYqDpYt40Qp1zFmepDfZqq28,5540 +aiohttp/base_protocol.py,sha256=5JUyuIGwKf7sFhf0YLAnk36_hkSIxBnP4hN09RdMGGk,2741 +aiohttp/client.py,sha256=6s0n3HM4CRk4Gl7f-umGBTGp2MiVA7yjMnGLhSkxZXs,46918 +aiohttp/client_exceptions.py,sha256=4NmjMG2-P__buR9xfuz8_w0pvbXzr2oyuo62eQxsea8,9445 +aiohttp/client_proto.py,sha256=eHQjoiZVvm1m31Vcj1H2huV-zwHZwEl71km5l-p22aE,8624 +aiohttp/client_reqrep.py,sha256=xfJsqTzkfmRfAzChxp1za6NcfU2GDo1XXkEhTnJDik4,39756 +aiohttp/client_ws.py,sha256=nNrwu1wA0U3B0cNsVr61QfV2S60bbKfaZXHfW7klFl4,11010 +aiohttp/compression_utils.py,sha256=GCkBNJqrybMhiTQGwqqhORnaTLpRFZD_-UvRtnZ5lEQ,5015 +aiohttp/connector.py,sha256=YnUCuZQSgseKsUbo2jJH7mi0nhZD_8A6UQ5Ll3GS37k,52834 +aiohttp/cookiejar.py,sha256=PdvsOiDasDYYUOPaaAfuuFJzR4CJyHHjut02YiZ_N8M,14015 +aiohttp/formdata.py,sha256=q2gpeiM9NFsl_eSFVxHZ7Qez6RbM8_BujERMkooQkx0,6106 +aiohttp/hdrs.py,sha256=uzn5agn_jXid2h-ky6Y0ZAQ8BrPeTGLDGr-weiMctso,4613 +aiohttp/helpers.py,sha256=hYm60xCxbJCdtdtLhTt7uspQ_9HPT27gTBx2q9Fu1Zk,30255 +aiohttp/http.py,sha256=8o8j8xH70OWjnfTWA9V44NR785QPxEPrUtzMXiAVpwc,1842 +aiohttp/http_exceptions.py,sha256=7LOFFUwq04fZsnZA-NP5nukd6c2i8daM8-ejj3ndbSQ,2716 +aiohttp/http_parser.py,sha256=99kVO47hO22HQV0B4Y-1XhtOK4LTISOOs2d8c9yOGqQ,35166 +aiohttp/http_websocket.py,sha256=5qBvfvbt6f24AptTHud-T99LEnpwbGQNLMB8wGcfs9c,26704 +aiohttp/http_writer.py,sha256=fxpyRj_S3WcBl9fxxF05t8YYAUA-0jW5b_PjVSluT3Y,5933 +aiohttp/locks.py,sha256=wRYFo1U82LwBBdqwU24JEPaoTAlKaaJd2FtfDKhkTb4,1136 +aiohttp/log.py,sha256=BbNKx9e3VMIm0xYjZI0IcBBoS7wjdeIeSaiJE7-qK2g,325 +aiohttp/multipart.py,sha256=rDZYg-I-530nIEq3-U4DF2Q-fl0E7mk5YEmejzT1bak,32492 +aiohttp/payload.py,sha256=IV5HwxYqgUVY_SiyPjzUQ_YUIYvQaGkrmLmZlvSzDeM,13582 +aiohttp/payload_streamer.py,sha256=eAS8S-UWfLkEMavRjP2Uu9amC3PnbV79wHTNDoRmYn8,2087 +aiohttp/py.typed,sha256=sow9soTwP9T_gEAQSVh7Gb8855h04Nwmhs2We-JRgZM,7 +aiohttp/pytest_plugin.py,sha256=3IwpuxtFiUVFGS_ZitWuqvECSGgXQWvCW312B2TaVLY,11605 +aiohttp/resolver.py,sha256=8peXjB482v0hg1ESn87op6f-UeLXk_fAMxQo_23Ek6M,5070 +aiohttp/streams.py,sha256=vNCy0k5R7XlnSfsyTEQAkBD4Q9tNZgNm76Gqlw8Tjok,20836 +aiohttp/tcp_helpers.py,sha256=BSadqVWaBpMFDRWnhaaR941N9MiDZ7bdTrxgCb0CW-M,961 +aiohttp/test_utils.py,sha256=t5ibahuV6klkUMLdWuJhlSvzwtIRprWZqnkdHt5XJYU,20205 +aiohttp/tracing.py,sha256=Kz9u3YGTegGebYM2EMhG9RKT6-ABcsHtM7J-Qvwyus8,15152 +aiohttp/typedefs.py,sha256=8pDFTXt-5sYdzE4JsRH6UjAQyURnfZ0ueeEtgQccQZU,1491 +aiohttp/web.py,sha256=HFTQaoYVK5pM3YmxNJtZl9fGrRIdFs_Nhloxe7_lJj0,19263 +aiohttp/web_app.py,sha256=sUqIpip4BUL_pwc-Qs7IWDrFpeBNpaD8ocKFGfs9B0U,18351 +aiohttp/web_exceptions.py,sha256=7nIuiwhZ39vJJ9KrWqArA5QcWbUdqkz2CLwEpJapeN8,10360 +aiohttp/web_fileresponse.py,sha256=VRmCEr-qz7hzBy1TPa1wzid2DkgtQdIDjA18amkmRO4,10705 +aiohttp/web_log.py,sha256=DOfOxGyh2U7K5K_w6O7ILdfGcs4qOdzHxOwj2-k3c6c,7801 +aiohttp/web_middlewares.py,sha256=imxf1cfCKvfkv_jQLfTNNs_hA95oLnapRcPl-2aDsdA,4052 +aiohttp/web_protocol.py,sha256=REP-s4onglMYW2XZ5nqPCWkK0vt_2f8FiP4MFQXdd3A,23064 +aiohttp/web_request.py,sha256=TGTsWNNpGSL49Q-uV101ecX9hLZMm5PFs4mLxGkbkHc,28776 +aiohttp/web_response.py,sha256=t8mNgT5nddHLpsgZhUkqbq0NDsipmdGzfZUB5pCP_Yo,27749 +aiohttp/web_routedef.py,sha256=EFk3v1dcFnLimTT5z0JSBO3PShF0w9sIzfK9iJd-LNs,6152 +aiohttp/web_runner.py,sha256=AM3klOcr72AZVGG9-LjYo8vJdHBWgNzDLsmi_aoKfAU,11736 +aiohttp/web_server.py,sha256=5P-9uPCoPEDkK9ILbvEXmkkJWPhnTxBzdwAXwveyyDk,2587 +aiohttp/web_urldispatcher.py,sha256=sblBVycAhKVFPRbtfbZGsrPivLL0sskeE3LRPK2Deec,39557 +aiohttp/web_ws.py,sha256=eGjrE3_lUbv9kpYZluZFvdCfvahi5O4-fF7hWgyEHQk,18039 +aiohttp/worker.py,sha256=bkozEd2rAzQS0qs4knnnplOmaZ4TNdYtqWXSXx9djEc,7965 diff --git a/venv/lib/python3.10/site-packages/pytest-7.4.3.dist-info/REQUESTED b/venv/lib/python3.10/site-packages/aiohttp-3.9.1.dist-info/REQUESTED similarity index 100% rename from venv/lib/python3.10/site-packages/pytest-7.4.3.dist-info/REQUESTED rename to venv/lib/python3.10/site-packages/aiohttp-3.9.1.dist-info/REQUESTED diff --git a/venv/lib/python3.10/site-packages/aiohttp-3.9.1.dist-info/WHEEL b/venv/lib/python3.10/site-packages/aiohttp-3.9.1.dist-info/WHEEL new file mode 100644 index 0000000..1d81251 --- /dev/null +++ b/venv/lib/python3.10/site-packages/aiohttp-3.9.1.dist-info/WHEEL @@ -0,0 +1,6 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.42.0) +Root-Is-Purelib: false +Tag: cp310-cp310-manylinux_2_17_x86_64 +Tag: cp310-cp310-manylinux2014_x86_64 + diff --git a/venv/lib/python3.10/site-packages/aiohttp-3.9.1.dist-info/top_level.txt b/venv/lib/python3.10/site-packages/aiohttp-3.9.1.dist-info/top_level.txt new file mode 100644 index 0000000..ee4ba4f --- /dev/null +++ b/venv/lib/python3.10/site-packages/aiohttp-3.9.1.dist-info/top_level.txt @@ -0,0 +1 @@ +aiohttp diff --git a/venv/lib/python3.10/site-packages/aiohttp/.hash/_cparser.pxd.hash b/venv/lib/python3.10/site-packages/aiohttp/.hash/_cparser.pxd.hash new file mode 100644 index 0000000..65e3d4b --- /dev/null +++ b/venv/lib/python3.10/site-packages/aiohttp/.hash/_cparser.pxd.hash @@ -0,0 +1 @@ +f2318883e549f69de597009a914603b0f1b10381e265ef5d98af499ad973fb98 /home/runner/work/aiohttp/aiohttp/aiohttp/_cparser.pxd diff --git a/venv/lib/python3.10/site-packages/aiohttp/.hash/_find_header.pxd.hash b/venv/lib/python3.10/site-packages/aiohttp/.hash/_find_header.pxd.hash new file mode 100644 index 0000000..f006c2d --- /dev/null +++ b/venv/lib/python3.10/site-packages/aiohttp/.hash/_find_header.pxd.hash @@ -0,0 +1 @@ +d067f01423cddb3c442933b5fcc039b18ab651fcec1bc91c577693aafc25cf78 /home/runner/work/aiohttp/aiohttp/aiohttp/_find_header.pxd diff --git a/venv/lib/python3.10/site-packages/aiohttp/.hash/_helpers.pyi.hash b/venv/lib/python3.10/site-packages/aiohttp/.hash/_helpers.pyi.hash new file mode 100644 index 0000000..6a30d63 --- /dev/null +++ b/venv/lib/python3.10/site-packages/aiohttp/.hash/_helpers.pyi.hash @@ -0,0 +1 @@ +6682a22524b9d4fc442e123672622be7bdfb6238d9709b7b15b2113b7ca6d52b /home/runner/work/aiohttp/aiohttp/aiohttp/_helpers.pyi diff --git a/venv/lib/python3.10/site-packages/aiohttp/.hash/_helpers.pyx.hash b/venv/lib/python3.10/site-packages/aiohttp/.hash/_helpers.pyx.hash new file mode 100644 index 0000000..8f38727 --- /dev/null +++ b/venv/lib/python3.10/site-packages/aiohttp/.hash/_helpers.pyx.hash @@ -0,0 +1 @@ +5de2db35fb795ffe227e2f1007c8ba4f2ad1b9aca28cc48edc80c779203cf6e3 /home/runner/work/aiohttp/aiohttp/aiohttp/_helpers.pyx diff --git a/venv/lib/python3.10/site-packages/aiohttp/.hash/_http_parser.pyx.hash b/venv/lib/python3.10/site-packages/aiohttp/.hash/_http_parser.pyx.hash new file mode 100644 index 0000000..8e5a141 --- /dev/null +++ b/venv/lib/python3.10/site-packages/aiohttp/.hash/_http_parser.pyx.hash @@ -0,0 +1 @@ +7f32b0c1595c1a71957a218ece8d3977ed9171caad97df8fcd82aa80addfc5d2 /home/runner/work/aiohttp/aiohttp/aiohttp/_http_parser.pyx diff --git a/venv/lib/python3.10/site-packages/aiohttp/.hash/_http_writer.pyx.hash b/venv/lib/python3.10/site-packages/aiohttp/.hash/_http_writer.pyx.hash new file mode 100644 index 0000000..8e1aaab --- /dev/null +++ b/venv/lib/python3.10/site-packages/aiohttp/.hash/_http_writer.pyx.hash @@ -0,0 +1 @@ +6881c0a7c838655e646c645d99971efaf5e310bc3633a7c62b226e39d81842ac /home/runner/work/aiohttp/aiohttp/aiohttp/_http_writer.pyx diff --git a/venv/lib/python3.10/site-packages/aiohttp/.hash/_websocket.pyx.hash b/venv/lib/python3.10/site-packages/aiohttp/.hash/_websocket.pyx.hash new file mode 100644 index 0000000..ddbb4c7 --- /dev/null +++ b/venv/lib/python3.10/site-packages/aiohttp/.hash/_websocket.pyx.hash @@ -0,0 +1 @@ +d57b8e48d0c26f20ebcc5e6e300da2b2a6aeb12b3c9768d64cb0e53432ccf48a /home/runner/work/aiohttp/aiohttp/aiohttp/_websocket.pyx diff --git a/venv/lib/python3.10/site-packages/aiohttp/.hash/hdrs.py.hash b/venv/lib/python3.10/site-packages/aiohttp/.hash/hdrs.py.hash new file mode 100644 index 0000000..e0b81d7 --- /dev/null +++ b/venv/lib/python3.10/site-packages/aiohttp/.hash/hdrs.py.hash @@ -0,0 +1 @@ +bb39f96a09ff8d789dda1fa4cba63464043c06b3de4c62c31abfb07a231cb6ca /home/runner/work/aiohttp/aiohttp/aiohttp/hdrs.py diff --git a/venv/lib/python3.10/site-packages/aiohttp/__init__.py b/venv/lib/python3.10/site-packages/aiohttp/__init__.py new file mode 100644 index 0000000..32f85ac --- /dev/null +++ b/venv/lib/python3.10/site-packages/aiohttp/__init__.py @@ -0,0 +1,240 @@ +__version__ = "3.9.1" + +from typing import TYPE_CHECKING, Tuple + +from . import hdrs as hdrs +from .client import ( + BaseConnector as BaseConnector, + ClientConnectionError as ClientConnectionError, + ClientConnectorCertificateError as ClientConnectorCertificateError, + ClientConnectorError as ClientConnectorError, + ClientConnectorSSLError as ClientConnectorSSLError, + ClientError as ClientError, + ClientHttpProxyError as ClientHttpProxyError, + ClientOSError as ClientOSError, + ClientPayloadError as ClientPayloadError, + ClientProxyConnectionError as ClientProxyConnectionError, + ClientRequest as ClientRequest, + ClientResponse as ClientResponse, + ClientResponseError as ClientResponseError, + ClientSession as ClientSession, + ClientSSLError as ClientSSLError, + ClientTimeout as ClientTimeout, + ClientWebSocketResponse as ClientWebSocketResponse, + ContentTypeError as ContentTypeError, + Fingerprint as Fingerprint, + InvalidURL as InvalidURL, + NamedPipeConnector as NamedPipeConnector, + RequestInfo as RequestInfo, + ServerConnectionError as ServerConnectionError, + ServerDisconnectedError as ServerDisconnectedError, + ServerFingerprintMismatch as ServerFingerprintMismatch, + ServerTimeoutError as ServerTimeoutError, + TCPConnector as TCPConnector, + TooManyRedirects as TooManyRedirects, + UnixConnector as UnixConnector, + WSServerHandshakeError as WSServerHandshakeError, + request as request, +) +from .cookiejar import CookieJar as CookieJar, DummyCookieJar as DummyCookieJar +from .formdata import FormData as FormData +from .helpers import BasicAuth, ChainMapProxy, ETag +from .http import ( + HttpVersion as HttpVersion, + HttpVersion10 as HttpVersion10, + HttpVersion11 as HttpVersion11, + WebSocketError as WebSocketError, + WSCloseCode as WSCloseCode, + WSMessage as WSMessage, + WSMsgType as WSMsgType, +) +from .multipart import ( + BadContentDispositionHeader as BadContentDispositionHeader, + BadContentDispositionParam as BadContentDispositionParam, + BodyPartReader as BodyPartReader, + MultipartReader as MultipartReader, + MultipartWriter as MultipartWriter, + content_disposition_filename as content_disposition_filename, + parse_content_disposition as parse_content_disposition, +) +from .payload import ( + PAYLOAD_REGISTRY as PAYLOAD_REGISTRY, + AsyncIterablePayload as AsyncIterablePayload, + BufferedReaderPayload as BufferedReaderPayload, + BytesIOPayload as BytesIOPayload, + BytesPayload as BytesPayload, + IOBasePayload as IOBasePayload, + JsonPayload as JsonPayload, + Payload as Payload, + StringIOPayload as StringIOPayload, + StringPayload as StringPayload, + TextIOPayload as TextIOPayload, + get_payload as get_payload, + payload_type as payload_type, +) +from .payload_streamer import streamer as streamer +from .resolver import ( + AsyncResolver as AsyncResolver, + DefaultResolver as DefaultResolver, + ThreadedResolver as ThreadedResolver, +) +from .streams import ( + EMPTY_PAYLOAD as EMPTY_PAYLOAD, + DataQueue as DataQueue, + EofStream as EofStream, + FlowControlDataQueue as FlowControlDataQueue, + StreamReader as StreamReader, +) +from .tracing import ( + TraceConfig as TraceConfig, + TraceConnectionCreateEndParams as TraceConnectionCreateEndParams, + TraceConnectionCreateStartParams as TraceConnectionCreateStartParams, + TraceConnectionQueuedEndParams as TraceConnectionQueuedEndParams, + TraceConnectionQueuedStartParams as TraceConnectionQueuedStartParams, + TraceConnectionReuseconnParams as TraceConnectionReuseconnParams, + TraceDnsCacheHitParams as TraceDnsCacheHitParams, + TraceDnsCacheMissParams as TraceDnsCacheMissParams, + TraceDnsResolveHostEndParams as TraceDnsResolveHostEndParams, + TraceDnsResolveHostStartParams as TraceDnsResolveHostStartParams, + TraceRequestChunkSentParams as TraceRequestChunkSentParams, + TraceRequestEndParams as TraceRequestEndParams, + TraceRequestExceptionParams as TraceRequestExceptionParams, + TraceRequestRedirectParams as TraceRequestRedirectParams, + TraceRequestStartParams as TraceRequestStartParams, + TraceResponseChunkReceivedParams as TraceResponseChunkReceivedParams, +) + +if TYPE_CHECKING: # pragma: no cover + # At runtime these are lazy-loaded at the bottom of the file. + from .worker import ( + GunicornUVLoopWebWorker as GunicornUVLoopWebWorker, + GunicornWebWorker as GunicornWebWorker, + ) + +__all__: Tuple[str, ...] = ( + "hdrs", + # client + "BaseConnector", + "ClientConnectionError", + "ClientConnectorCertificateError", + "ClientConnectorError", + "ClientConnectorSSLError", + "ClientError", + "ClientHttpProxyError", + "ClientOSError", + "ClientPayloadError", + "ClientProxyConnectionError", + "ClientResponse", + "ClientRequest", + "ClientResponseError", + "ClientSSLError", + "ClientSession", + "ClientTimeout", + "ClientWebSocketResponse", + "ContentTypeError", + "Fingerprint", + "InvalidURL", + "RequestInfo", + "ServerConnectionError", + "ServerDisconnectedError", + "ServerFingerprintMismatch", + "ServerTimeoutError", + "TCPConnector", + "TooManyRedirects", + "UnixConnector", + "NamedPipeConnector", + "WSServerHandshakeError", + "request", + # cookiejar + "CookieJar", + "DummyCookieJar", + # formdata + "FormData", + # helpers + "BasicAuth", + "ChainMapProxy", + "ETag", + # http + "HttpVersion", + "HttpVersion10", + "HttpVersion11", + "WSMsgType", + "WSCloseCode", + "WSMessage", + "WebSocketError", + # multipart + "BadContentDispositionHeader", + "BadContentDispositionParam", + "BodyPartReader", + "MultipartReader", + "MultipartWriter", + "content_disposition_filename", + "parse_content_disposition", + # payload + "AsyncIterablePayload", + "BufferedReaderPayload", + "BytesIOPayload", + "BytesPayload", + "IOBasePayload", + "JsonPayload", + "PAYLOAD_REGISTRY", + "Payload", + "StringIOPayload", + "StringPayload", + "TextIOPayload", + "get_payload", + "payload_type", + # payload_streamer + "streamer", + # resolver + "AsyncResolver", + "DefaultResolver", + "ThreadedResolver", + # streams + "DataQueue", + "EMPTY_PAYLOAD", + "EofStream", + "FlowControlDataQueue", + "StreamReader", + # tracing + "TraceConfig", + "TraceConnectionCreateEndParams", + "TraceConnectionCreateStartParams", + "TraceConnectionQueuedEndParams", + "TraceConnectionQueuedStartParams", + "TraceConnectionReuseconnParams", + "TraceDnsCacheHitParams", + "TraceDnsCacheMissParams", + "TraceDnsResolveHostEndParams", + "TraceDnsResolveHostStartParams", + "TraceRequestChunkSentParams", + "TraceRequestEndParams", + "TraceRequestExceptionParams", + "TraceRequestRedirectParams", + "TraceRequestStartParams", + "TraceResponseChunkReceivedParams", + # workers (imported lazily with __getattr__) + "GunicornUVLoopWebWorker", + "GunicornWebWorker", +) + + +def __dir__() -> Tuple[str, ...]: + return __all__ + ("__author__", "__doc__") + + +def __getattr__(name: str) -> object: + global GunicornUVLoopWebWorker, GunicornWebWorker + + # Importing gunicorn takes a long time (>100ms), so only import if actually needed. + if name in ("GunicornUVLoopWebWorker", "GunicornWebWorker"): + try: + from .worker import GunicornUVLoopWebWorker as guv, GunicornWebWorker as gw + except ImportError: + return None + + GunicornUVLoopWebWorker = guv # type: ignore[misc] + GunicornWebWorker = gw # type: ignore[misc] + return guv if name == "GunicornUVLoopWebWorker" else gw + + raise AttributeError(f"module {__name__} has no attribute {name}") diff --git a/venv/lib/python3.10/site-packages/aiohttp/__pycache__/__init__.cpython-310.pyc b/venv/lib/python3.10/site-packages/aiohttp/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..30f371939814b7bd2180375b3b02cf2863fb21a4 GIT binary patch literal 4158 zcmb`KTUXmi5`ZP!V89p*7zmdnHt{(;$1T{x7w{si z>vpyg0PrQe#8>bNU&CvB18>j(1K+}1G{M9j*ue@^&;knu2&_UCcVQQ6P(vGRT@wlU zFY;y1d53%E9y+E&x_bacQn*%cH8G?K&+41fto)&*ny~tAPpbZ+V+%Kk1-9qTNlD_f$Hbm23K`f{yJ7|+YLGwCAd(KAkd{izQAH-I zIM#PD27>PR_YLu5L->K}ikfZ+zwWud zP<_YC$A&Bk-=~>3`+PP!R#~=Dc#S~CliLzIC9kq4g3r2hWJ^GcSU#x7tCN|x-CZH; z(sl#YvE=TXj%{sjEUVsC6NR;I*N?a$6WhGxRz1~U5^`V2ld~I)#Iv?viCib*B^in; zj*Pcr`)CH0n(B?JV)LUJcb1Fmv2OBmuIza$rhBj@TRbtP>Y8mJl4IgB1zp>`Cvnyc%tBJNQ{dT^=|)gf*<#JK z-4!!(ze>%O&D}y;WjG?93mG~id!#UVLmewls;daSZElAfswK!X^K!A?oT2;|pyHS(RI#-dh!HFFRMe`&2*?hMP#Qg&&L_kXjPdE$F+vyg7Sbe94O z+}%SR*+|f=&z8l9;IKBcD*~e)?NikmzZe0pIYodUNI`R!6am?A9<*&dheBL+&x)!^ z3;9`)E7z#4#WRYU6k1evZe_i^ZNzJ$+Buk>HAI74JLgr&E>f=Y^N#nPJu5xuut8^3 z8!b;Er!r+}R(Ri4?Ok=IS>QZKQBWjBl-pb@^ti9azF1!h$mem3lNv&+tgmZ`>iSXYNBq)Oj)EKEp@*G;>T`7tJ9a?5Gvd!jp+%-VV*! z5EWtXQ`!ud<+p?yTxhs<#gp#l^JUMgQ!d!@ zaf$H_<1*t4qrkYz_?B^vah);Am|)ysj5BUBZZU2%?lA5$zGK{DJYYOzOfeoYzGwWv zxX*aZm}bl{ii}yt9Alobz*uA~F`hD>FqRoBj8(=OW1aDgvB4-Y%8X6MbH*0q1!J25 zjF*g8jMt1ejJFJvFs{;uVLFass8%V0hIA_=J@(L5T4URN_H#fN>OqaJN{LlEsye7I z9Y3C=?xe@^Duz+eqqA(nFl^Tj3}d{05RPcm(0^y+*vd9t z*62s2_)MtqHLdw1>4nV?=fNxRiRQ6Sk@B&_N?5w1q5cznB>a@nwJpuUcH zD3U)&!o~iWM$c+EV(eq@Sk3guTyM+_=up^c1Y)evs#AG=q%wI1IGj%I&^SJUR}wHsI+kRtJX?*%l#j+K#o=QFv2VlCV(&% zd^HP%yZg_kf(xvfvw&)^g!iU*raVjts;x@*CyS1}Fg2)jO*nO$?TH*gcY6x9lbIq+ z4TqUxG8_b(7neVgG*mi1%cK18$0_77PvmRaL*<&cg63@?%qOZ%#s~e0%%aj!J3q=U zVV*gjVpKYy!<>En|IRhZa`Bx*kO*1)Fae(dKRScHYqqB8TGT;oP F{tK3!#U%g$ literal 0 HcmV?d00001 diff --git a/venv/lib/python3.10/site-packages/aiohttp/__pycache__/abc.cpython-310.pyc b/venv/lib/python3.10/site-packages/aiohttp/__pycache__/abc.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5686e5d53123766014b2fe5f9156fb44e68a30ca GIT binary patch literal 8575 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 VTb*8~V>H_sX%|TiR_;f? zg7!N$W*-+CU%;zA1HlQWG3nwzi)o*AS>Ne8lyHZ;vD+_n3zU3Gc!3w+5MC5yw|L-m zOPoF-e(5c&Cmo*|@6kXcC)&vm;+QR0s9wiz#R?~QvvyJdcnmwDxl+pU1Mz^kAwfL7ym(5j#< z@&;%%z686~C%Y~473f>!P0xE_CW+`F5-?L~vlvJb@+f(puPAF7CTfsM zZCtxoQ3^ejNvFqVpg|qqyYQ;(AjWh|0*DhxKnWz^1k$m(%gGaR%sTh{f-J#g>7gj2 zSU^z)0U>ggh__`88plK~!QY?H+B<1qv|og(e+&GP?Y&gKP=l~1+V?g#H`*^n^0FOA zTkXNV-bs^B)<6EJt-#Y;Fy%$~T&Q*!r8`;=+FO8AFqi19rjZQcm)~2Jdhr(Q}=I|jV{gkIlpesQRD;Cz&iQ>6zk|UbkDkWV^l&N9i!x1 zl2l!y9qsDEwzGeULLVj1NJ96r96E6potCSxonM^hikU=alDek8p|y;*hFWY&nM$+L zN%j5yAQpX*Xu-42>f>Reqkc9;o)K?(1&;X;-an9w4axw6W$IG)Plb7Zof+|o0sjqz z{C|M3k)H)3kL1J zE2f<>6ukpR$(TXuvqdipT|wd^Qwr67(u-14ewx= zBO=%A9Hcgp??WGWo98LC3S*kLeA)r(qF2*|l^3vyHO3v-G}kx6(AWILPxbA}^!BN+dB+O$}*1O_!TEfPu;e zHgn=g9|vxsm@>v{NE?ZHf+$+5c*Ld6GV>a=;=->|@tWRhq292wASM~KdVc_yo;@qe mUi+n(fud~M8ZZCwvMbLz*m$=ypgzazLE%m7L1nG7Rp(#8c7DYG literal 0 HcmV?d00001 diff --git a/venv/lib/python3.10/site-packages/aiohttp/__pycache__/client.cpython-310.pyc b/venv/lib/python3.10/site-packages/aiohttp/__pycache__/client.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6c23a9c04fe07e2fbc7a1ae625703152178ea4d3 GIT binary patch literal 30921 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.10/site-packages/aiohttp/__pycache__/client_proto.cpython-310.pyc b/venv/lib/python3.10/site-packages/aiohttp/__pycache__/client_proto.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6825a13802b95e836b39474b50f05ea57392e359 GIT binary patch literal 6507 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.10/site-packages/aiohttp/__pycache__/client_ws.cpython-310.pyc b/venv/lib/python3.10/site-packages/aiohttp/__pycache__/client_ws.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d25e3235b03820a99ccb93ad32362731da141132 GIT binary patch literal 9194 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℘zhOCv0qb($)_Cc;-guX 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 AQ2+n{ literal 0 HcmV?d00001 diff --git a/venv/lib/python3.10/site-packages/aiohttp/__pycache__/cookiejar.cpython-310.pyc b/venv/lib/python3.10/site-packages/aiohttp/__pycache__/cookiejar.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..81f7612a9327c4de4b2f2872014ea2559fb7a523 GIT binary patch literal 10806 zcmai4TWlQHd7k^u&R)1&QY3X>%eJJIEK-tX%XSncv?N-JCCU*cyIxD!4EG$0yWAVk z%u3>B7Is1f4wATrlOjb?qaj^1h!CJf(hK_5qDa#g$U~l5VA?+Q$q#+XQc3&~olkgBB%X<27f zhiaKZM&`}xa4lQN)8!wF4CJGa^-G$w?J%v5By@kECeT98; zj8)xVJ5V@)e2m%EgSA{CS9_}Plq|=qhiXq3o|bv1`b_QF!n3u*g~PQYg(Ij-6pr$} zJkOE~+HR$A>@|g@So)#D(mc1S7mlMm#4;#nWcfL!&MEofPYGwUc{NNhzTmd1J}Z~} zydFBQx*osKXe^a^XkBXvk5|LwT)Eb)c55-jDDiSNp9m8dqoEfuG&E#tU-7wc=c_!7 za(+XE#?`Xthp}sJvstd+4ejeqzuc(1)li?~erVoo0d{EKs-qI1UC+l9`mGyRKg9tm zc`Y2Cp7(s=mi%a0Uvfn_dZEf)5tZK%9BX%dj$x^?S1dP+E@Ogw9=4Ob+GzCls*EaFiVY%@Ra| z9b`H5JjTAr4zZ_^JI+QpsF^*(o<-?7Hq8#RBgma#NBN{2m&e#+-SG}P&YnZ-6hFC2 zYdgVDu}OYX!nW8HJBis&ciT?M`qP;6d3FZ%&+{`9-U|ea?_03gi|p(}V^uA@fR>j~ z`|@VXE9`T!7P$aF|D>i>F_FVX}Sao!bvizI^+^>60gr%-nwP{Hw^EIeGiR z3zT^gnX{BRd+E-xqhEOAoZndD_471{2Aq5e&onZpW^S`r+!O8T69x30LCws}o#XlT z{-d|qS5G~6x|4rphg>_pS=oN=XvMAPgQE+49%;=LNZlr-HFpJRrA4ikR+Z{ocaeI$ ziL?PJ5*)2J?jdEol#gJZe(p>s5B{|LcDcUL5F4vRGDJ$X z(P)aN3C=T=JWI)8B%vj^-xBo_$r<{Q>M-%>-N*YrGQP5^RMfy=>IYysU-R`}nMTLG0~friu$pio+h*nd~)==FnsJkWh-RTD=8eJ!!B+*MIKDr=LdeZhAEz}jqy zF^%aDl|a7;mhb3+4u~l~?GLRDkq|&BQ>Gb*nF0bcW*@0?7n`fcS``Q)xq~^py<Z_P_$kzfDv+CMIQAsd3Uehh{shR0Z(^L0&{oYiyJU`W3 z@fREQ=O<5{obt*(KY=bwU<7Zidf6`)C(l(ICAaFGN2WJ0pAAi~)#M_yd))+KNYEjpE}GE8PN9dg z#l?o_7aR5J%4S2X*rl{ov7gigVOF9>JV&|X1a7>zGyMsRS`EKk<6)*)=MVfMf6y!o zmy}?b>??SAGc=%Y7j`1nM`{=^7E4vv^NPg?{ZlmeX(U(ictp`@TT?aF+OTy?wNx9A zqbBiWR7=w|>$jGXMyZ{B5`AHOpKu%R-o;e z_JR6gwga|UGXkYz2FO*cb)B>m14kLkD)L0KrxY)DA1p-wxA90a)@MWQ{ya8k)EbQE zV_}Sz0CEWp($%5SX!3e!HOr+X7zqYz)tcgYEF;WZzVh1TOLI4iZ``vhBu7oFqw1lJc6tjH)Zw%Dei=h|-1|^>@6h*PPIHG{ z@85CPVqQ!GX5JEnTXB(+OO(tYA))0mxl?h4DmVE}?nYcgT?5bNX42a>L&L*(vu7F& z?2GJY;sAN3Cn5X-SS}D-`+UV$*VL}Cfk3O^OxJNzbgr8@E6UO7LCk5<2#71{9a~o} zDtGd?wcA?9=$IWVu)1{zP!sFeff1NNY(ekDgE&T5_pn`h2l3qyd`qhLC5TgdRTb9* zZOsl~$EeEkspTVqCgJK5YD}&t4p^}TU>)mw66&&q+O7XQ?LenqWFfCP0qpv94Up%k zm-w!qATe37){^VWCpg7Q>Lc}r^84@|#!+8{w=hTT>0Ums%|;q*NHVv08ORi`P$Egw zERk_4UZ4unW}yjjSMx#(1~9L)FviMW$rUWrn~i3u*Px@Udv3MGJy_beUM*gm{=MSO zE7vZCb}yHABHAVfgbr;K8LV=>-nbuHtWk4G$r0OxRwU6vqwYf5ShdJ}*O&Opb^^K% zNIo9#B_uYu!I6JMH8fitgTQdq3C&Wq4M)@7(|(Uw|~6?09I?`E@11fF>iIOhihSf|uzI{3ecpsk?D_ z(117unhsV)rU z)n^BW&_3z$=ov*q`@CazAfVo){eWobVAMnlANKmkM%8T;P{`UY{3a$a|fWL zHUEU_1(No}XMqfj`-{9T-l3j%kd%mhdq2w4Be#DD85pEwR>LHPRK*)54MsGi^n!j) zN(50xx4bTH;uT3(5~g`GIuR0H5r1z^xD6Sgcxh<$9E5d@kW@gbe_$H}-NIN&I%*Kc zh#@o^b&nTe@s@~^d%rX?aXfN2e~&W?vB6ihc2zbYW*qY4{MG!AD10~c#qs6>y za5vPJDz0JPIJ6q!F4Bj(L8H(QuwrWoIrcWRp2^g2bRl+Ib-_W1$siW!>zc=x$5C%# zPCKwgGq5o>wyt{L!v0cJ*DZgQL6_Ij7TC;0ZJK%!+Usgf*H9QsNg9PHGEI-H;>Wv=+=YMlECk5RS1^Wo?`UsN^cAJ#?$d z5Jc*_>E2|dT|#v-RHV3(g#B2ZYR78sgA;IE!dOH%UVb99g?nH64+0@cMT}8$j1qEg z#T%3m19u${7y;h>vcD)zgiuGcDswd%r($aW~+FR7_ zyh&!mB_R-HOTc(Xw5~{-h=e=wZ9W~b%RL%%pT^ka=8%0LnN-}SI^Bm^Ep?fkmUdXA z9j0Js_%8X=4JD0@Bmt%vAN8#NhVWilG0pr9_vuGQ|sDADq zoIXqlz6?vtC!T&hv=!`(LVP64dq2{4o32lRm|UqS!h#pA)HPoN!%7p`O9!wOJjlqSS1a)G@2MRlFvRPCW{cM;iqJ8Au&!u~@{)9z)F##A^(MPeyOX ztNO!IOjS{~$&f~E7UuX0O8Sy69xTt*VQ7OfQHE%EkH)QyVh_6j7mSU%Z=uc*AE69} z6dx_m`D4t1$v;l>`xC3^1NMdhSldlCat&5x4>^Z0|9h(;TQ1Rl>4uEOcsYNu?Bx&y zIMR!lc{#YIplQx>^DAiPWs!4zUzF!tKKCZY3eE|k36o+7@a6A&-k?o^lapo$sZo#k zkyH;^k0WV`T1hbq(l1+l8$}la)PjU}@Xog2dE zOwJmb(yQnbNzx_x3iV5q$xJ>&WH3isdzf1EyWEe2)fp-#O7P2BQ9|muA>XD3yLT$$ zAr&d|UT^r_*jt$Hu~`&4l42$jI3#95yC%FU0o{h{Fg=LT|3+u;Dw2$@ZCE-vCzfL{tKN7Np7j> zFCs&h%9185(i&h@kP`G`ijfFVneL-?RZS>T%fg<4iD#gFKeb1Jw~iCr`?qYs5>l}MZHLmJWU|AR8o#xj66VCY@sw(y3EHL6&rgv{mb znEFH1ogqw-3qmT1R8E8^0}2V)>bHUiI4QJA0T&TF9iSs}f9SqQqUT2d9jG2c%5%6^ z>}Ys&JO&<g|;uM@}P;DL=)=|9X zGC@WxqL&e5Dsi$qJ@zH#>nfEx4tkqj8@-)w@8Murw9(rH%;7G~#PR`V2e^~cl_gbt zgT;}4r;a#G-3$`UIjeLM^$gQDdwicI(Bos-!$6ND^q31m0Y zKGR2b(nQ4J5}%U2^_lz%FAb**6HeaH+#K))$;u#JkzOtZMWmM-i=y&VB89~llJ{6`fPT#y#ym|fgOS5yu8;DhrrHVM02+g~q(Q1Yn+2QiFIT_2M zE~b=lp(WeBa8!1!qqEKEWbGXQ9!mv!aZ=$D=%RSHeV;twE74_WG6#TjTRsw z9tXn$np9f7p-yAWsJ9{eCFGHI73ZJInP`rvB8;PdH=B^7x&?>Eb_WfH6T~WDyhaDe zkTD=`42T>0C%bPFIfj;$braBQy*zB+%{(mRz7ksAn}CL#fL6=^N*0f{VW>I0_i622 zeZs!aZ6J^PktUAdB^Hr@<|sd#e}y7JvPf}l15ROx_*y~g=*t|RQD$JH0B$PsvspO zc2h!fS)`~X=6WmjQn|79$-yODU-|fkpeZj}1Tm|4of3+`i(QmZJWyT%iMy1mQnE}5 z`Cp-l$z5N%YmrSiPEFsUgkmaT5R$hpc(vNQ z0+NLEc>+eEM|tN=2jbDq75`F}H}_G{bH9&54m|6~tj%?UGm(9zSTqaV=s855VavmnGFJlUKo=OrOB z@QZaz-Vc%K|2%?eO#fJA1~<4_L9hW~2?%rfSpli6G(Nl8%i_$zl|I4?JdRK?gg;6S zJ{?G5RH8SArP&ZJ@ss#)Ar(Pk!z_!QX+9)B1sLHOHp+)&o5gmqG4vj0d8aNU8t5p zH*>JfamkmXxLB^<=z(a8Mu}RWLg4DuIfsGWg*PjXVt#qAD>-jdZ9qE6z^INlJ>7V! zyqZ>{t7#cUhdVLPB5sD<%I%32{RtzrRK~Da3aY@np=4K{+n|o$9t$yCsuJ zpnQ{-OruC9AF?1vjR`%g@6-3_QsCrIM638I0r?&!zd#ad4KKPJo}sFLp+u@WIAPT) zFVP1Cp8TL8Oj7V;vKzRLLJ4FHM)5`icGqI`8G_}n&^LI3WV5g-iBsZGBGq7$auO|R z$_-H>HRUjJp|RrP+Yj0ZqL%X|LGF=Mf?;ftSMi~RCtXr#?v*B_+JYBhNoq|}wRCaf zO6_%=N={^j;QFg3Rfxnz9}*=xNlu0Kxya-=4@4^7BoZ69mV^JK3Z6;`*dBg^CQWiN(Z&;~YPf-g`c6+$4jH}A3mW+`wLVM0o!oz~d$&PcN@ z&DtI`6ovP7`2~5fQ+WuLzmZ>|r&Q&M$GnBB^PQf{+8e51TsrD*b+@|veCPX)oX*a= z8h-y+yd9V4H0|G1Ir-C3xq>VE1V(6rb+jhkd53j*lXvx|-Zh#=*KC@M+VzgrwVSpo z8y%-xX;!*!(?#7BR%fQ`HNEa^bGBP;R#ltbnd{b?HC1*x)$V+A9_31N;g%*`G4oIp zGY7o6DA@a2-TM-2&>D54)UPJ{rE@!vWO%m|mB!m~mY2?*ULL1O*r~I!(wChmX{92n z^Rn_zzmvx|;#OW(r^R<=y0`x&!-RFcG`5Az%1W2|^+K7Kb}!uTq@lo6)vbP%WkEZ} zgkd*Ai~a8Op>s2p-5X&ZwrFW5f3v7u!Ik|JCeoTrXiY9yQ?KxfUNOW)VMaz|E^ESy zEMZ4>AIwC8*`~5VJ;1N}`Tic<` zqP(M+ zZov*`j=~0!c+LLJu0E`5xr#9{clbyS97AILACN0E*Ae1L&w_j`yEHxy!gXb2%Ai#lyNkA=wvGK*wt6Z7@v`gYol*6)Q`ceS5Ia(y?I_p)Bt ziq>yl-MG5`AW9yrcjCM2z5RSUOGKKm}Kop z28wRtWUDlYCQ9=`*y%^IPOJDOnHOPNIt=~j?%*Z`<-8*F!Z-``e#XY5ubg<( z6`&iV5BSg+kA15!3QHKTgY0Pyu{N{|9dBb&V4SXI{Rp#~g+88DFYJ6~$D52$Yh&_b z^fp!R485(w9QQU=@7ZZ@ZKrxTS6GEz@V0)$#xZ0!HsuOVXRtCkb8twO`*$PohChM3 zpSJxhmuvp5G=I&%oT;N8yfk$Voc2?O4c?fJc+#CeWq7?NFJh@BkJHk+edk(;fFTr? z=H2}~g0Rpnlrr2e-EP!P<^F>>+LbnS(XsrpvWbOmNSR7Wx*vwM4ZrqaRska3tjS^9JM!=_|ldEJ-Be3)c$U&;4GAr%v zC=`**o(z=Alq(3f*)&=EFCMxZIaxY1DlP`%;277ms&b}X~Ya=wQjP#GFS*hL& zL=h`KA-nGP!dPa0*y*IZky!D;w*K1md|s$4iS0pa!7VRFy~=CsDd42axDNbq z<2&y^gT?>HM0N@k6eR=PPyJ4|O=79l|YwEra&joSb|gF5KaY zwk}X9VS>Z0rr>b%WuXtP1j!VU($FF>0Z0s#hAj1werO$Opg{W(hnPE!5f7RDyuJGB z;L?v~EQ8e_?eaT+EZuKh&Nh*unj+1j`=v9{5U3c1ZQWL}Eq{ZuygOIN7z8tB?dhaOJD?gasoSQ2MU8U@Gz>OTeW(wfA#W7?fhM2J z%^ga{IJLmDNX~X3b&rgpkyIwnRGa~An_h_8`>2iY+oZwPLT)!Rz0 z3iC5A%%S;-xyyDzFS1n_J2MpvNwU#so>o~IM`Ri3;ZW{|xo@u#UE*g}H1_v`wI-hYc@l{aDPTu#e_SFb}U6A%OBC=NUE zAo5jG;*$hGEv4&^>3>vF52@IYXJ|so3pW8ps?-nBn$k~I5?20(+rN|%gkc^Ocs6e6I7;r+MK#AB9)CPA>clmQXg5#HQlOK?IiwtG6Pwoxrb_?eh z1b@fFi~^B_+2NroF(NW8GwsGp@Qo%AUPN;e3)spxm8>;?Gz2s`5^b`XfP6$DbJ z_tLUU<8GREBxSVn6*8Bp(E(=hwV3bA7pP6C4+(_AiX@>QEuR=d(xyjSs#58XWFE7X zDp4A*zC)=;_9l#@;lOQjhw-h&`S;`w%i!N~)BT1o!i5e!v%cb!XD0u~viR4Q&A;M~ z``?AN+WFeT!eupUeM#vhWNyEQNL3pfZISX4wYky*P2$UE4Uj@$(W#FAb3pR0R8{F~ zIgSjqGZh&~w&=?_8woLO0H1(?a*%rY5AC&dULSej_XLlOLekOB19>&R#68>%zM%gP DXIgv4 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.10/site-packages/aiohttp/__pycache__/hdrs.cpython-310.pyc b/venv/lib/python3.10/site-packages/aiohttp/__pycache__/hdrs.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2df2570abf4d68927129479586a6b086c5f0617d GIT binary patch literal 5198 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.10/site-packages/aiohttp/__pycache__/helpers.cpython-310.pyc b/venv/lib/python3.10/site-packages/aiohttp/__pycache__/helpers.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6d056ea225bc83719664742c9b1ac8fe4a073739 GIT binary patch literal 29567 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 N_olj&-SG{v{|lB&`LO^1 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.10/site-packages/aiohttp/__pycache__/http.cpython-310.pyc b/venv/lib/python3.10/site-packages/aiohttp/__pycache__/http.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9b776158f96606d801e61090a11a7f14e485c8b6 GIT binary patch literal 1467 zcmaJ>OLN;c5C$oUdQp-szaO!cG)b$rG3~ZTPo^G+jx()ojp#;GbUYkdgl*b{$N-d8 zH}R?c2mJ%dvD1HqYfriN)Qh`INp_k;p~1)9)dz4F`+-rVQX(+^oO%&_l_%scbUqw8 zaK42p{|5Mh_{7&jLh-4G8rP|=p`8gcJWI1YM|0q3{ak49Jk6)J5f*rn7E?PPmUx+# zxk*i4p%q@GRbHbtK0zna{|n(HuhTl8qEmdDPV*T$!)NI%pQCepp3d_Hy1*CdB446Q ze3>q%Yl`7LzCu^{DqZCb+Ti!;eTbLn1Hb(C6GqM-viv)pK4OpAr@pyK{EA=wjWGTE zv$xH6RoXo~Y&nUS^kq|5`8_X= zgWjpi9rojpz1M&Rq_XQ?G9iPg=epod?Iuaw5>b~)31nM{NHh&q*=3#&w3a6%6Rm?%9sB z_1sqVQRv0~^V^sKvG433Rkg#8ov3@kk~CLUyY<^?${z$eB1jv;YBgwWSpQ&QU z8K;A$JUzUMnJNvW%YIJWQ`S@F$d0kxW>%H4xR=8tgLAoaHrD^^ZYa}91oQatz0G+6 zf+echww>0&zGFM8=Gg6*cH4Chb`CpRZCfk@K`bCFA}k>k5Q+$>d{IU)5h@5(gc`yG zLLFfWVGdy)VFqCmVHRN;;Fm`0DmjaK*6a13){m~&;IVOZ-SC3w4C=97Z(NHNpmk9r z zO1l)Rut5s{w5+p;GwZ^W{7E0gWep;6A!D!0teq`;%erK}ODhbHtvF5j>$Qz_OF|=` z0ObN?B&~7UnEP6MrAnKA&`r7#kDu;fvIc-bs+w^l9^q^60W`l<8F?jD78ePuO&Q4* z-Vc#MP8K6X;8ImX4(OrF(jw%rkL5I)szUsKlT8Xo`dd@D<>U zAA_oK^`#fWZBEZgWrALE*R7%T5H~7p^2qOp>?w*w;@%} z?h)%%Ehnh*$yK=nRmqXce>7L;5YE2gB+l>kj6QZ3>}0n3ZNHxGd9UB^eJN=)surHV zAGsbq#q4WhmLCVqWeoif5MgyK!2}<&5$|$Fb9-oyoUSt}bxYtKQ5w3Va<`1R+x0%S zL|J%`E#b+^BfDD#UlCRCRapgJ178z$@O4=OUkBe1E8tfQ-vGZVn&6wVf%hxm-wwOQiA>$z zbefF(ULxdqe}t=r+tLqX;SXdeq|(L5=>1lH+FaZm*fesALtFm2q%dU5&-WB>@@>WU z_HDbC%kb00zbkzWJp3pIYI+Q)pik%zBc1w5-yesO(ng#jcrvTgvNMOY%F2b9tXzmt z1cIXIP&L}?Dw$4H+@pi^*ccQY9OFX_{T9d`keV=6-eY@KVC~tUY>$I-VZq7_^78M} zC(XxOPUdJi>>m*K+)^vp^72pZK{Ar<`=K6Pm}sfmJBhll$6-&lKlK!x;fiEw*gL=`;#q<4NhT_T6@+#CfFnX5LURypICzLu_x9u8y2uTQ||lI+T2UF zsxS%JFgGop) zmKY|;WdYhf`w17$voaJebzZWIQ@-U-t8<-y`;uJ(Zj~VD4MVMifGin@BN+q> z7+cKT;@}0aQ^_%I@N-DlbaDT+*3LJ!R@4TzWz`@UC1Ns!l&l^EpHISJE|ZKRlHRg# z5puF6LN+#;ppJT8ZU)5G?C4_TTXeG%kv8WYY_CQ7n|^o0aQ=}Xvs_e-nKgeNMGSGwMp zh_G%2C{(kRE1@8iKgpBJi@yNMmo+T&Q#j4sh56VpS`lR?O7=)cZ= z^%1~Fv?9BHg^ISv`V_f&wDJx-ah?F4+gV9Z#>kCi!psTPwubAHr!H+`>L@K3vl?U4 z=Jzp-vo=|sO^@xr$vmoBRfDrSP9HcyWRZbLR|Dq)#H>-cUr!E)zRAv^9@i?T_!ely%1CZV_*Ba6@K>GbrKP)pwgr>R@xd{bW7bmzHq zP+LlXOBP%x`ZK(^gz*izjb}kkT6wLSib96e`Y?%86r%lV9V=FSS!i2S+KxJhW3QA? zS>g)4Y!M;u2W20v~AJTsTc^}QC6ZeDC*NQvdknwgpFcp0IUMEQ* zHJNM=d=aLh!9<5?r2D&Q0gR>mp*c`A44dC$lJijG_j9F)JERl^4n6ukkeSz17QWnB$E2H*NjJ3{Q7msK$&Do5&dPV<`*E@pXU-6-S!o!JqBN_8 zy%ae((9u*XN;RDxImzsyIq6|z=yyO;>yhoS3ZX2Mv0PsItXKb>iKtfQ>p!P)76&0GmzBggyi>r#x|7?DJ>^YNo~v(iB<0s zIZI^W))Y;jVwl9>+8fOIu4zx#moB|XOI&TzVInQ}VBnaXL0U)heEO#eB(cg_8dZSW=&0^XDA9LZRyBTpQB)K6bzsS1kndSu6I#9P%x-$ zyIJFU{2&}gBJU0}dmvPS(ySujtNSYg1v8Ix#b*BuZTxPBoHkLUbFzNC?h6I22mG? zRhROqLDpA@kKaOzk1sAzXRC!L-TCZ>N%Hi|=72Wlf(v4V-)+ w#z)P)jS5StYYt52UCcY~OJoV{gE&^*Mi+h($ literal 0 HcmV?d00001 diff --git a/venv/lib/python3.10/site-packages/aiohttp/__pycache__/http_parser.cpython-310.pyc b/venv/lib/python3.10/site-packages/aiohttp/__pycache__/http_parser.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..49bca33ee82f0da067ffd0671a6f2f330addb5d1 GIT binary patch literal 19093 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 I|I+*a2YDW}8UO$Q literal 0 HcmV?d00001 diff --git a/venv/lib/python3.10/site-packages/aiohttp/__pycache__/http_websocket.cpython-310.pyc b/venv/lib/python3.10/site-packages/aiohttp/__pycache__/http_websocket.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..753ed494f322d133ed3acd658f8432256f156904 GIT binary patch literal 15619 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

  • lnAT0ttqQyjak!39ox1h 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 delta 11328 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 literal 9893 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 b)+v3GHLkBeUAR4=_ntS?xIue>mty) zL?VZ%D2m!Srjg)M^)K|)OQou}UUJ!MPU*3|RjTNlH3aCa-rM&zPxC(i%Du@i`02Dm z@Vv=RSFW|=Uoyv!hRjVuXz(i%jFbqYOsUeEQc|d-)v8+4N?Jl|RlTI6Os^U>vt&~8 z-OBY6@>(@p%JK|1c}E}VBip3Z$%a@PYk#Vhx_I_cH_Nh)=adbzZr1Zu+oq)+qoonmKMfej+n$Fw<8JSRrz-Jv3lQ;VMGhl^pw_X1?CxezMe>h!%CWR&T9 z;u(D~3MM{3Hh_l>@M3Mo_xLhJ(cw{$un|7TP{lrdOKtI|(ob_jSJ!B_c&z4WUc69y zPlJwDC$6d}H~=bsQI}~}3~LuD6%VxUN6w%!w)`qvhf2sHhb#<8nH_RoPK4va)o;=^ z@r6Dyc~$%ZO zXmH}0^4$_-gux~fA&JN%`Za-TqTN^+d`X!3O5LMaNivr}(V797BPd98UhEiSlRfpt zAmmFGuMqGcz>JSo{ne|5m^wu_c{s=rVlCi&E!Kc9vRGfP`T>V=>Ewb$7sbEE6?#TY zn5Qp)B&}12P8(2dg?E&vj)U+T-n#%N#JA?atsbiV4WG&D7HUH7g~30mAcR6xSdtlm zVbq<-fNb;>LXv?2DqfpITS>R-HKU0g91&(PlUYBgO`X|IW3waDA|p~GeVgu@%-N$X z^<3Gt_Q&M3)_`NK8ArxM z#;1ek6+yV2KFKN+ca6SK*GBadIWfV_N%Plfb8FbQd9z z17aC2O+H1V3O%hD`x(`s7X4)3dXiCksP#nIcTv|xT_w5M>Tg^8?BsB2Upv2D!bM2n zvP2&vdb^KjQ!k8--6#}0l4*o7scb_O>jAEw0IrKaQeRS4jJYFe`5d@`sAxh#EV!q- z-;dwhPjE!y#CcCh6-d1mC^tm-|4l*@cJzPy&;Qz)|FMs!{+MWa z>9j-O`Tn14?d}L6k8yMSF<`R{+$O`27l@lmS1VYqf z;d%ADzDzS}&RAHPLH$|OYxQ7Do&&C`$v6lLq&$!6I7St@I8e5?9SA?DH$Au2-snql zT77FwTzCZxP8<_!tOMcg{96D43CT@*i^$9BFJo=0K!y~GNmgU3TGu7!MxI7Ervh_! zrPOQqfmm<56<_#1q;j_7Z7f#K`c5?!A6nw^Bi$EbGt#AK@W^O(JYUFJ3~~|SW%ZkR znNF*UHN88B*2$xwec3s<+VdRc4zJdC|GV5g`1s=h4u!>RiYvt&Ae-i>{-<2Vs5UqR z!VUZrDZt|5AjB#74l4Asb!Imt+~6j+zS0Idw+F^fE@Yt*Y9X}bp1~7C%AI>`&m59H zizkP4VBR2ZK_`K|y-dl#qGZS6sn9Br8^k0Z6V3+KZ40s(zqjZX3F%&fr$ckd26kx6 z)zElAcqX*>96mA}qYubK4a~ElStNY42nDF-)KmMLF$+@S^@IxzoB&op7PwH45^ohM zaEYDldV^P_@Oz%?3oL`mI&aS>BPKY+2WTLQM4kgucUJp}ZLts)aV8eKoqAV6dveKNAe=goTCZFv#x{;zcM3OKI-`4s@$ zOi@@&r;JWBb3dcysr?1pM@b&!Xsto3sdtkTP?e43kMr=CA`*)&Uj=xw56_NKnVr2_ zspjH&kQY#y0f-DgXav4oRO_ivC{sVAX43e1xdc$PAneoB_}EJ*c?}`%uY)F2Q|U>% zrWVte7FuXrj;wl}d(C=XUV?qZ+MdL}LSzQpJ@}bO4Flj5K~CbaQ-7yhw~ck=4@O`T zmG-Bex7qIYIuge)Zb^9tx%gc8sbi;j-tYEO*W>+;xQdhK<1pz2x;tJrJ9r~zw4COo M^E8()j}}V*0&MY4F8}}l diff --git a/venv/lib/python3.10/site-packages/_pytest/__pycache__/pathlib.cpython-310.pyc b/venv/lib/python3.10/site-packages/_pytest/__pycache__/pathlib.cpython-310.pyc index 894b9f05b64b89264539c2f6876a9a76bd2c3c16..3f9114c0a684e928832054bd0fbcc0cce2e22e86 100644 GIT binary patch literal 29116 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 delta 10859 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 delta 25472 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 Zjt2;J;+PIa0cug8;QXLHx&D#v)h`V(zA*p* delta 842 zcmZXRy^a$x5XbHD+WV2s65v9z0j@foSRoxWG)IHPhiC{A2hBFoDRxc^o6X{E3M8a! z=+S8ll)=c8r)28|j~l;xEi`Zg8DdvZ zm}h#e)SceK;xp)H(bp7H9wxLGvuU~}&&dupMcyG%@8H#)E7q-KSVXjP?&n1^$#zr2 zJL>67WkZF`AI|c$hDRChAAF9kS0dk?PL-_e>60YS5yWUWNzJTPG1FOpXyNOg4D%r- zzKLFbpbsvY1>G>>qu?1VRaxX|{4b)HHp|=ap0-M#T`afRM){a+Q1bRA+ooxqL|5AA zt_edUJ(Nf+cQUK1`+a@hEM73Fc!51tME-vlsLRT$yj$MmTXeI0&aZXWO$F2S;eRA@ zN-Dy#CHUojKME>)Fp!hsU{HzTS)OEj8cZu@#Jq&&Mnw5Ww0pKe2c=?3R_IPm(?aX@ z@}t-+a*QqFbDu5@dpxcYz9BT Tf+OJY{iI4@m2hDWUhw4?;hUi1 diff --git a/venv/lib/python3.10/site-packages/_pytest/__pycache__/python.cpython-310.pyc b/venv/lib/python3.10/site-packages/_pytest/__pycache__/python.cpython-310.pyc index 344ad6b30fc1d8942e7d1a2911c2d6e04806a8ad..56c1966493b2f8650cfa4b7801c5911dfc721a23 100644 GIT binary patch literal 51805 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