diff --git a/.gitattributes b/.gitattributes index a6344aac8c09253b3b630fb776ae94478aa0275b..3b2390e64ad20c2ce3edfc6a3f8381d386579953 100644 --- a/.gitattributes +++ b/.gitattributes @@ -33,3 +33,87 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text *.zip filter=lfs diff=lfs merge=lfs -text *.zst filter=lfs diff=lfs merge=lfs -text *tfevents* filter=lfs diff=lfs merge=lfs -text +my/correlation_evolution_over_time.png filter=lfs diff=lfs merge=lfs -text +my/auxiliary_ablation_plots.png filter=lfs diff=lfs merge=lfs -text +my/refined_aux_comparison.png filter=lfs diff=lfs merge=lfs -text +my/ablation_study_summary.png filter=lfs diff=lfs merge=lfs -text +my/aux_7vs4_comparison.png filter=lfs diff=lfs merge=lfs -text +my/correlation_explanation.png filter=lfs diff=lfs merge=lfs -text +docs/webui.png filter=lfs diff=lfs merge=lfs -text +docs/conceptual.png filter=lfs diff=lfs merge=lfs -text +my/auxiliary_metric_correlations.png filter=lfs diff=lfs merge=lfs -text +shinka/favicon.png filter=lfs diff=lfs merge=lfs -text +docs/code_evolution_history_panes.mp4 filter=lfs diff=lfs merge=lfs -text +ccevolve/dataclaw_export.jsonl filter=lfs diff=lfs merge=lfs -text +py311/lib/python3.11/site-packages/pillow.libs/libpng16-d00bd151.so.16.49.0 filter=lfs diff=lfs merge=lfs -text +py311/lib/python3.11/site-packages/30fcd23745efe32ce681__mypyc.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text +py311/lib/python3.11/site-packages/pillow.libs/libharfbuzz-fe5b8f8d.so.0.61121.0 filter=lfs diff=lfs merge=lfs -text +py311/lib/python3.11/site-packages/pillow.libs/liblzma-64b7ab39.so.5.8.1 filter=lfs diff=lfs merge=lfs -text +py311/lib/python3.11/site-packages/pillow.libs/libxcb-64009ff3.so.1.1.0 filter=lfs diff=lfs merge=lfs -text +py311/lib/python3.11/site-packages/pillow.libs/liblcms2-cc10e42f.so.2.0.17 filter=lfs diff=lfs merge=lfs -text +py311/lib/python3.11/site-packages/pillow.libs/libjpeg-8a13c6e0.so.62.4.0 filter=lfs diff=lfs merge=lfs -text +py311/lib/python3.11/site-packages/pillow.libs/libtiff-13a02c81.so.6.1.0 filter=lfs diff=lfs merge=lfs -text +py311/bin/python3 filter=lfs diff=lfs merge=lfs -text +py311/lib/python3.11/site-packages/scipy.libs/libquadmath-828275a7.so.0.0.0 filter=lfs diff=lfs merge=lfs -text +py311/lib/python3.11/site-packages/scipy.libs/libgfortran-8f1e9814.so.5.0.0 filter=lfs diff=lfs merge=lfs -text +py311/lib/python3.11/site-packages/scipy.libs/libquadmath-96973f99-934c22de.so.0.0.0 filter=lfs diff=lfs merge=lfs -text +py311/lib/python3.11/site-packages/scipy.libs/libgfortran-040039e1-0352e75f.so.5.0.0 filter=lfs diff=lfs merge=lfs -text +py311/lib/python3.11/site-packages/matplotlib/_image.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text +py311/lib/python3.11/site-packages/matplotlib/_qhull.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text +py311/lib/python3.11/site-packages/pillow.libs/libopenjp2-56811f71.so.2.5.3 filter=lfs diff=lfs merge=lfs -text +py311/bin/python3.11 filter=lfs diff=lfs merge=lfs -text +py311/lib/python3.11/site-packages/pillow.libs/libwebp-5f0275c0.so.7.1.10 filter=lfs diff=lfs merge=lfs -text +py311/lib/python3.11/site-packages/pillow.libs/libfreetype-083ff72c.so.6.20.2 filter=lfs diff=lfs merge=lfs -text +py311/lib/python3.11/site-packages/pillow.libs/libavif-01e67780.so.16.3.0 filter=lfs diff=lfs merge=lfs -text +py311/lib/python3.11/site-packages/pillow.libs/libbrotlicommon-c55a5f7a.so.1.1.0 filter=lfs diff=lfs merge=lfs -text +py311/bin/ty filter=lfs diff=lfs merge=lfs -text +py311/bin/python filter=lfs diff=lfs merge=lfs -text +py311/lib/python3.11/site-packages/matplotlib/_path.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text +py311/lib/python3.11/site-packages/matplotlib/_tri.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text +py311/lib/python3.11/site-packages/matplotlib/ft2font.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text +py311/lib/python3.11/site-packages/matplotlib/_c_internal_utils.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text +py311/lib/python3.11/site-packages/contourpy/_contourpy.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text +py311/lib/python3.11/site-packages/numpy.libs/libgfortran-040039e1-0352e75f.so.5.0.0 filter=lfs diff=lfs merge=lfs -text +py311/lib/python3.11/site-packages/numpy.libs/libquadmath-96973f99-934c22de.so.0.0.0 filter=lfs diff=lfs merge=lfs -text +py311/lib/python3.11/site-packages/scipy.libs/libscipy_openblas-6cdc3b4a.so filter=lfs diff=lfs merge=lfs -text +py311/lib/python3.11/site-packages/distlib/t64.exe filter=lfs diff=lfs merge=lfs -text +py311/lib/python3.11/site-packages/distlib/w64-arm.exe filter=lfs diff=lfs merge=lfs -text +py311/lib/python3.11/site-packages/distlib/w64.exe filter=lfs diff=lfs merge=lfs -text +py311/lib/python3.11/site-packages/distlib/t64-arm.exe filter=lfs diff=lfs merge=lfs -text +py311/lib/python3.11/site-packages/rapidfuzz/fuzz_cpp_avx2.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text +py311/lib/python3.11/site-packages/rapidfuzz/fuzz_cpp.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text +py311/lib/python3.11/site-packages/rapidfuzz/process_cpp_impl.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text +py311/lib/python3.11/site-packages/numpy.libs/libscipy_openblas64_-fdde5778.so filter=lfs diff=lfs merge=lfs -text +py311/lib/python3.11/site-packages/rapidfuzz/utils_cpp.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text +py311/lib/python3.11/site-packages/propcache/_helpers_c.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text +py311/lib/python3.11/site-packages/multidict/_multidict.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text +py311/lib/python3.11/site-packages/jiter/jiter.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text +py311/lib/python3.11/site-packages/kiwisolver/_cext.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text +py311/lib/python3.11/site-packages/charset_normalizer/md__mypyc.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text +py311/lib/python3.11/site-packages/__pycache__/typing_extensions.cpython-311.pyc filter=lfs diff=lfs merge=lfs -text +py311/lib/python3.11/site-packages/scikit_learn.libs/libgomp-e985bcbb.so.1.0.0 filter=lfs diff=lfs merge=lfs -text +py311/lib/python3.11/site-packages/yarl/_quoting_c.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text +py311/lib/python3.11/site-packages/yaml/_yaml.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text +py311/lib/python3.11/site-packages/scipy/_cyutility.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text +py311/lib/python3.11/site-packages/sklearn/_isotonic.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text +py311/lib/python3.11/site-packages/sklearn/_cyutility.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text +py311/lib/python3.11/site-packages/aiohttp/_http_parser.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text +py311/lib/python3.11/site-packages/aiohttp/_http_writer.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text +py311/lib/python3.11/site-packages/PIL/_imagingft.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text +py311/lib/python3.11/site-packages/PIL/_imaging.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text +py311/lib/python3.11/site-packages/PIL/_imagingmath.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text +py311/lib/python3.11/site-packages/PIL/_imagingcms.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text +py311/lib/python3.11/site-packages/frozenlist/_frozenlist.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text +py311/lib/python3.11/site-packages/pydantic_core/_pydantic_core.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text +py311/lib/python3.11/site-packages/Levenshtein/levenshtein_cpp.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text +py311/lib/python3.11/site-packages/pydantic_core/__pycache__/core_schema.cpython-311.pyc filter=lfs diff=lfs merge=lfs -text +py311/lib/python3.11/site-packages/grpc/_cython/cygrpc.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text +py311/lib/python3.11/site-packages/attr/__pycache__/_make.cpython-311.pyc filter=lfs diff=lfs merge=lfs -text +py311/lib/python3.11/site-packages/fontTools/varLib/iup.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text +py311/lib/python3.11/site-packages/fontTools/misc/bezierTools.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text +py311/lib/python3.11/site-packages/fontTools/cu2qu/cu2qu.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text +py311/lib/python3.11/site-packages/fontTools/qu2cu/qu2cu.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text +py311/lib/python3.11/site-packages/botocore/__pycache__/utils.cpython-311.pyc filter=lfs diff=lfs merge=lfs -text +py311/lib/python3.11/site-packages/fontTools/pens/momentsPen.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text +py311/lib/python3.11/site-packages/fontTools/feaLib/lexer.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text +py311/lib/python3.11/site-packages/botocore/__pycache__/credentials.cpython-311.pyc filter=lfs diff=lfs merge=lfs -text diff --git a/ccevolve/dataclaw_export.jsonl b/ccevolve/dataclaw_export.jsonl new file mode 100644 index 0000000000000000000000000000000000000000..32ca9c26868772fd3bc444ed5ecc21b3e60cac83 --- /dev/null +++ b/ccevolve/dataclaw_export.jsonl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:53728b02ef81ee69bc72ff737e505f5bb6d13d327254cdc1f551a9548f35f3cb +size 10932298 diff --git a/docs/code_evolution_history_panes.mp4 b/docs/code_evolution_history_panes.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..3da879d17b42b6e2c689c0437d31d1d9b9c56c6c --- /dev/null +++ b/docs/code_evolution_history_panes.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a90a7215e02977c250db579166772ca26bb481731c0e012a298e5fe598d78329 +size 502256 diff --git a/docs/conceptual.png b/docs/conceptual.png new file mode 100644 index 0000000000000000000000000000000000000000..45827c1d4e2cc9a900411a0bc438a001a8a78b36 --- /dev/null +++ b/docs/conceptual.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:eac2c3bf9ec79108b87e650a4fb2adf7844e0c32d160ec4d2b6547df222b0f1e +size 716899 diff --git a/docs/webui.png b/docs/webui.png new file mode 100644 index 0000000000000000000000000000000000000000..75ffeca1a2b592bad970838364d55ab9d3a5b930 --- /dev/null +++ b/docs/webui.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:721c8b96c2bb1a84ebe59596783d407b0bbab324cd3758c954deccd575ef5ffc +size 841867 diff --git a/my/ablation_study_summary.png b/my/ablation_study_summary.png new file mode 100644 index 0000000000000000000000000000000000000000..8eba8d403692bb278d99b7e719942c68bf090f50 --- /dev/null +++ b/my/ablation_study_summary.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e71ddc209a71804af23cee6ba1a2ea32dd0b665a7efb02e9dd6406253b94b578 +size 991319 diff --git a/my/aux_7vs4_comparison.png b/my/aux_7vs4_comparison.png new file mode 100644 index 0000000000000000000000000000000000000000..fa7a1d4b0a04e584e70797332028139f82d8dbfa --- /dev/null +++ b/my/aux_7vs4_comparison.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a0b523cde167360ab0ed98961ccaf11e8d8f14928ce16bbd7168ee5cf090bfbf +size 163600 diff --git a/my/auxiliary_ablation_plots.png b/my/auxiliary_ablation_plots.png new file mode 100644 index 0000000000000000000000000000000000000000..e026a5d69d2dbb855c5c15222fcbe09553d7f316 --- /dev/null +++ b/my/auxiliary_ablation_plots.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c6e5cbb58134035e68261f375baae5b99f81c58fd969e7ff6df20d74ef62c7ca +size 832835 diff --git a/my/auxiliary_metric_correlations.png b/my/auxiliary_metric_correlations.png new file mode 100644 index 0000000000000000000000000000000000000000..edd89e00523f61b5ac6f83d2982903ed138a736f --- /dev/null +++ b/my/auxiliary_metric_correlations.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5f36ad8907366bde65a36dc210e23754270b26eb8427dd811f84122879d558e0 +size 419534 diff --git a/my/correlation_evolution_over_time.png b/my/correlation_evolution_over_time.png new file mode 100644 index 0000000000000000000000000000000000000000..726db3ae1c072fc90af43a02e27913a3d4dca547 --- /dev/null +++ b/my/correlation_evolution_over_time.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b45c32295a9d53dd50933cff5f6bba862d7dcf7449dd14ba63bbdabaac513d86 +size 653326 diff --git a/my/correlation_explanation.png b/my/correlation_explanation.png new file mode 100644 index 0000000000000000000000000000000000000000..dec36dbaf11e0112867bd26b512a92d6383e9ced --- /dev/null +++ b/my/correlation_explanation.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d1257097a9b9bc0d40556e98851bc4b7454c893c315a9a30aed319999cee01ed +size 543619 diff --git a/my/refined_aux_comparison.png b/my/refined_aux_comparison.png new file mode 100644 index 0000000000000000000000000000000000000000..45b7a2e8284b092f24d2db895f12521c79a46c32 --- /dev/null +++ b/my/refined_aux_comparison.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:eea9757ddd89b4106c9c53033846cd5536472d3d3bd467bbdd4703b2a97419e9 +size 248868 diff --git a/py311/bin/python b/py311/bin/python new file mode 100644 index 0000000000000000000000000000000000000000..78abe51a7c8a53ec2b009dd7abc8f087e9b441ae --- /dev/null +++ b/py311/bin/python @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:55e8f2734d0e882df2013e591fc2cd91fc31a4fc4d82dd0e78326af8fd3abdc1 +size 23756024 diff --git a/py311/bin/python3 b/py311/bin/python3 new file mode 100644 index 0000000000000000000000000000000000000000..78abe51a7c8a53ec2b009dd7abc8f087e9b441ae --- /dev/null +++ b/py311/bin/python3 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:55e8f2734d0e882df2013e591fc2cd91fc31a4fc4d82dd0e78326af8fd3abdc1 +size 23756024 diff --git a/py311/bin/python3.11 b/py311/bin/python3.11 new file mode 100644 index 0000000000000000000000000000000000000000..78abe51a7c8a53ec2b009dd7abc8f087e9b441ae --- /dev/null +++ b/py311/bin/python3.11 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:55e8f2734d0e882df2013e591fc2cd91fc31a4fc4d82dd0e78326af8fd3abdc1 +size 23756024 diff --git a/py311/bin/ty b/py311/bin/ty new file mode 100644 index 0000000000000000000000000000000000000000..1f0147bfe141fbab2df505f1328ba9a555f9d3b7 --- /dev/null +++ b/py311/bin/ty @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c50510e61c0d7bcaf2bb30cd9dca61be9b1b164321b97f0580138909d8bef954 +size 22765536 diff --git a/py311/lib/python3.11/site-packages/30fcd23745efe32ce681__mypyc.cpython-311-x86_64-linux-gnu.so b/py311/lib/python3.11/site-packages/30fcd23745efe32ce681__mypyc.cpython-311-x86_64-linux-gnu.so new file mode 100644 index 0000000000000000000000000000000000000000..b265405f0f953f442927669662e0c57e0e6f5b5c --- /dev/null +++ b/py311/lib/python3.11/site-packages/30fcd23745efe32ce681__mypyc.cpython-311-x86_64-linux-gnu.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:816d8ee44739af6e291f26efa53b5a267dd666a092580cf3a4701e4ebffaea6c +size 4314072 diff --git a/py311/lib/python3.11/site-packages/Levenshtein/levenshtein_cpp.cpython-311-x86_64-linux-gnu.so b/py311/lib/python3.11/site-packages/Levenshtein/levenshtein_cpp.cpython-311-x86_64-linux-gnu.so new file mode 100644 index 0000000000000000000000000000000000000000..30405ae845acdbdff8e0dd718e3213007d7f73b0 --- /dev/null +++ b/py311/lib/python3.11/site-packages/Levenshtein/levenshtein_cpp.cpython-311-x86_64-linux-gnu.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:94f6dd7f87f4f8ee2b6754571ef3ec48634bba145496f446e0d074a072b77e5c +size 447280 diff --git a/py311/lib/python3.11/site-packages/PIL/_imaging.cpython-311-x86_64-linux-gnu.so b/py311/lib/python3.11/site-packages/PIL/_imaging.cpython-311-x86_64-linux-gnu.so new file mode 100644 index 0000000000000000000000000000000000000000..99ccb19a4212fe9097fa478f83d5a501ae3e2c31 --- /dev/null +++ b/py311/lib/python3.11/site-packages/PIL/_imaging.cpython-311-x86_64-linux-gnu.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a088c23d0d939dea916cba106792e4a00587a3bef93cb4030f872e704af15818 +size 3361609 diff --git a/py311/lib/python3.11/site-packages/PIL/_imagingcms.cpython-311-x86_64-linux-gnu.so b/py311/lib/python3.11/site-packages/PIL/_imagingcms.cpython-311-x86_64-linux-gnu.so new file mode 100644 index 0000000000000000000000000000000000000000..6d8e8aebdc8a6fc09270b41b52123beaafc8c38e --- /dev/null +++ b/py311/lib/python3.11/site-packages/PIL/_imagingcms.cpython-311-x86_64-linux-gnu.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:64506a7eeb0f21196c226d962b13d558db0700d5f0b94c875404e1d8e7a5b0f5 +size 141369 diff --git a/py311/lib/python3.11/site-packages/PIL/_imagingft.cpython-311-x86_64-linux-gnu.so b/py311/lib/python3.11/site-packages/PIL/_imagingft.cpython-311-x86_64-linux-gnu.so new file mode 100644 index 0000000000000000000000000000000000000000..e19c5128d7ea77e301ec396470e701b6f0202de0 --- /dev/null +++ b/py311/lib/python3.11/site-packages/PIL/_imagingft.cpython-311-x86_64-linux-gnu.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f1660966e539c84b02033fc88aa7186782dbe3400125d6bf7e50d3cf30941b93 +size 306489 diff --git a/py311/lib/python3.11/site-packages/PIL/_imagingmath.cpython-311-x86_64-linux-gnu.so b/py311/lib/python3.11/site-packages/PIL/_imagingmath.cpython-311-x86_64-linux-gnu.so new file mode 100644 index 0000000000000000000000000000000000000000..03481750155557f8d6fd23e08ef9d3bedf8b0502 --- /dev/null +++ b/py311/lib/python3.11/site-packages/PIL/_imagingmath.cpython-311-x86_64-linux-gnu.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:210be7038a9a937f8ed3c43056f8163fa09bdd47e1b27606e68cb446200ee076 +size 161744 diff --git a/py311/lib/python3.11/site-packages/__editable__.shinka-0.0.1.pth b/py311/lib/python3.11/site-packages/__editable__.shinka-0.0.1.pth new file mode 100644 index 0000000000000000000000000000000000000000..7d059875b4d6a79f93261ffa80779fc70fba3a9b --- /dev/null +++ b/py311/lib/python3.11/site-packages/__editable__.shinka-0.0.1.pth @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d856a7c7cc50f7367291c07c65ff7fcc8438539cd5a4adc4204502ec56b3e1f4 +size 83 diff --git a/py311/lib/python3.11/site-packages/__pycache__/typing_extensions.cpython-311.pyc b/py311/lib/python3.11/site-packages/__pycache__/typing_extensions.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..74ed18b3b1892e899befe84fdde9f803fe505818 --- /dev/null +++ b/py311/lib/python3.11/site-packages/__pycache__/typing_extensions.cpython-311.pyc @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:afd7c53f001f4d3cec0e922645b62a56888e3cec2fc480e1a1492af8bea3a6af +size 179469 diff --git a/py311/lib/python3.11/site-packages/_pytest/_code/__init__.py b/py311/lib/python3.11/site-packages/_pytest/_code/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..7f67a2e3e0a6f34e04444d9410f517d59d21c422 --- /dev/null +++ b/py311/lib/python3.11/site-packages/_pytest/_code/__init__.py @@ -0,0 +1,26 @@ +"""Python inspection/code generation API.""" + +from __future__ import annotations + +from .code import Code +from .code import ExceptionInfo +from .code import filter_traceback +from .code import Frame +from .code import getfslineno +from .code import Traceback +from .code import TracebackEntry +from .source import getrawcode +from .source import Source + + +__all__ = [ + "Code", + "ExceptionInfo", + "Frame", + "Source", + "Traceback", + "TracebackEntry", + "filter_traceback", + "getfslineno", + "getrawcode", +] diff --git a/py311/lib/python3.11/site-packages/_pytest/_code/code.py b/py311/lib/python3.11/site-packages/_pytest/_code/code.py new file mode 100644 index 0000000000000000000000000000000000000000..add2a493ca78725f32be18799e886085998c6844 --- /dev/null +++ b/py311/lib/python3.11/site-packages/_pytest/_code/code.py @@ -0,0 +1,1565 @@ +# 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 +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 ClassVar +from typing import Final +from typing import final +from typing import Generic +from typing import Literal +from typing import overload +from typing import SupportsIndex +from typing import TypeAlias +from typing import TypeVar + +import pluggy + +import _pytest +from _pytest._code.source import findsource +from _pytest._code.source import getrawcode +from _pytest._code.source import getstatementrange_ast +from _pytest._code.source import Source +from _pytest._io import TerminalWriter +from _pytest._io.saferepr import safeformat +from _pytest._io.saferepr import saferepr +from _pytest.compat import get_real_func +from _pytest.deprecated import check_ispytest +from _pytest.pathlib import absolutepath +from _pytest.pathlib import bestrelpath + + +if sys.version_info < (3, 11): + from exceptiongroup import BaseExceptionGroup + +TracebackStyle = Literal["long", "short", "line", "no", "native", "value", "auto"] + +EXCEPTION_OR_MORE = type[BaseException] | tuple[type[BaseException], ...] + + +class Code: + """Wrapper around Python code objects.""" + + __slots__ = ("raw",) + + def __init__(self, obj: CodeType) -> None: + self.raw = obj + + @classmethod + def from_function(cls, obj: object) -> Code: + return cls(getrawcode(obj)) + + def __eq__(self, other): + return self.raw == other.raw + + # Ignore type because of https://github.com/python/mypy/issues/4266. + __hash__ = None # type: ignore + + @property + def firstlineno(self) -> int: + return self.raw.co_firstlineno - 1 + + @property + def name(self) -> str: + return self.raw.co_name + + @property + def path(self) -> Path | str: + """Return a path object pointing to source code, or an ``str`` in + case of ``OSError`` / non-existing file.""" + if not self.raw.co_filename: + return "" + try: + p = absolutepath(self.raw.co_filename) + # maybe don't try this checking + if not p.exists(): + raise OSError("path check failed.") + return p + except OSError: + # XXX maybe try harder like the weird logic + # in the standard lib [linecache.updatecache] does? + return self.raw.co_filename + + @property + def fullsource(self) -> Source | None: + """Return a _pytest._code.Source object for the full source file of the code.""" + full, _ = findsource(self.raw) + return full + + def source(self) -> Source: + """Return a _pytest._code.Source object for the code object's source only.""" + # return source only for that part of code + return Source(self.raw) + + def getargs(self, var: bool = False) -> tuple[str, ...]: + """Return a tuple with the argument names for the code object. + + If 'var' is set True also return the names of the variable and + keyword arguments when present. + """ + # Handy shortcut for getting args. + raw = self.raw + argcount = raw.co_argcount + if var: + argcount += raw.co_flags & CO_VARARGS + argcount += raw.co_flags & CO_VARKEYWORDS + return raw.co_varnames[:argcount] + + +class Frame: + """Wrapper around a Python frame holding f_locals and f_globals + in which expressions can be evaluated.""" + + __slots__ = ("raw",) + + def __init__(self, frame: FrameType) -> None: + self.raw = frame + + @property + def lineno(self) -> int: + return self.raw.f_lineno - 1 + + @property + def f_globals(self) -> dict[str, Any]: + return self.raw.f_globals + + @property + def f_locals(self) -> dict[str, Any]: + return self.raw.f_locals + + @property + def code(self) -> Code: + return Code(self.raw.f_code) + + @property + def statement(self) -> Source: + """Statement this frame is at.""" + if self.code.fullsource is None: + return Source("") + return self.code.fullsource.getstatement(self.lineno) + + def eval(self, code, **vars): + """Evaluate 'code' in the frame. + + 'vars' are optional additional local variables. + + Returns the result of the evaluation. + """ + f_locals = self.f_locals.copy() + f_locals.update(vars) + return eval(code, self.f_globals, f_locals) + + def repr(self, object: object) -> str: + """Return a 'safe' (non-recursive, one-line) string repr for 'object'.""" + return saferepr(object) + + def getargs(self, var: bool = False): + """Return a list of tuples (name, value) for all arguments. + + If 'var' is set True, also include the variable and keyword arguments + when present. + """ + retval = [] + for arg in self.code.getargs(var): + try: + retval.append((arg, self.f_locals[arg])) + except KeyError: + pass # this can occur when using Psyco + return retval + + +class TracebackEntry: + """A single entry in a Traceback.""" + + __slots__ = ("_rawentry", "_repr_style") + + def __init__( + self, + rawentry: TracebackType, + repr_style: Literal["short", "long"] | None = None, + ) -> None: + self._rawentry: Final = rawentry + self._repr_style: Final = repr_style + + def with_repr_style( + self, repr_style: Literal["short", "long"] | None + ) -> TracebackEntry: + return TracebackEntry(self._rawentry, repr_style) + + @property + def lineno(self) -> int: + return self._rawentry.tb_lineno - 1 + + 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) + + @property + def relline(self) -> int: + return self.lineno - self.frame.code.firstlineno + + def __repr__(self) -> str: + return f"" + + @property + def statement(self) -> Source: + """_pytest._code.Source object for the current statement.""" + source = self.frame.code.fullsource + assert source is not None + return source.getstatement(self.lineno) + + @property + def path(self) -> Path | str: + """Path to the source code.""" + return self.frame.code.path + + @property + def locals(self) -> dict[str, Any]: + """Locals of underlying frame.""" + return self.frame.f_locals + + def getfirstlinesource(self) -> int: + return self.frame.code.firstlineno + + def getsource( + self, astcache: dict[str | Path, ast.AST] | None = None + ) -> Source | None: + """Return failing source code.""" + # we use the passed in astcache to not reparse asttrees + # within exception info printing + source = self.frame.code.fullsource + if source is None: + return None + key = astnode = None + if astcache is not None: + key = self.frame.code.path + if key is not None: + astnode = astcache.get(key, None) + start = self.getfirstlinesource() + try: + astnode, _, end = getstatementrange_ast( + self.lineno, source, astnode=astnode + ) + except SyntaxError: + end = self.lineno + 1 + else: + if key is not None and astcache is not None: + astcache[key] = astnode + return source[start:end] + + source = property(getsource) + + def ishidden(self, excinfo: ExceptionInfo[BaseException] | None) -> bool: + """Return True if the current frame has a var __tracebackhide__ + resolving to True. + + If __tracebackhide__ is a callable, it gets called with the + ExceptionInfo instance and can decide whether to hide the traceback. + + Mostly for internal use. + """ + tbh: bool | Callable[[ExceptionInfo[BaseException] | None], bool] = False + for maybe_ns_dct in (self.frame.f_locals, self.frame.f_globals): + # in normal cases, f_locals and f_globals are dictionaries + # however via `exec(...)` / `eval(...)` they can be other types + # (even incorrect types!). + # as such, we suppress all exceptions while accessing __tracebackhide__ + try: + tbh = maybe_ns_dct["__tracebackhide__"] + except Exception: + pass + else: + break + if tbh and callable(tbh): + return tbh(excinfo) + return tbh + + def __str__(self) -> str: + name = self.frame.code.name + try: + line = str(self.statement).lstrip() + except KeyboardInterrupt: + raise + except BaseException: + line = "???" + # This output does not quite match Python's repr for traceback entries, + # but changing it to do so would break certain plugins. See + # https://github.com/pytest-dev/pytest/pull/7535/ for details. + return f" File '{self.path}':{self.lineno + 1} in {name}\n {line}\n" + + @property + def name(self) -> str: + """co_name of underlying code.""" + return self.frame.code.raw.co_name + + +class Traceback(list[TracebackEntry]): + """Traceback objects encapsulate and offer higher level access to Traceback entries.""" + + def __init__( + self, + tb: TracebackType | Iterable[TracebackEntry], + ) -> None: + """Initialize from given python traceback object and ExceptionInfo.""" + if isinstance(tb, TracebackType): + + def f(cur: TracebackType) -> Iterable[TracebackEntry]: + cur_: TracebackType | None = cur + while cur_ is not None: + yield TracebackEntry(cur_) + cur_ = cur_.tb_next + + super().__init__(f(tb)) + else: + super().__init__(tb) + + def cut( + self, + path: os.PathLike[str] | str | None = None, + lineno: int | None = None, + firstlineno: int | None = None, + excludepath: os.PathLike[str] | None = None, + ) -> Traceback: + """Return a Traceback instance wrapping part of this Traceback. + + By providing any combination of path, lineno and firstlineno, the + first frame to start the to-be-returned traceback is determined. + + This allows cutting the first part of a Traceback instance e.g. + for formatting reasons (removing some uninteresting bits that deal + with handling of the exception/traceback). + """ + path_ = None if path is None else os.fspath(path) + excludepath_ = None if excludepath is None else os.fspath(excludepath) + for x in self: + code = x.frame.code + codepath = code.path + if path is not None and str(codepath) != path_: + continue + if ( + excludepath is not None + and isinstance(codepath, Path) + and excludepath_ in (str(p) for p in codepath.parents) # type: ignore[operator] + ): + continue + if lineno is not None and x.lineno != lineno: + continue + if firstlineno is not None and x.frame.code.firstlineno != firstlineno: + continue + return Traceback(x._rawentry) + return self + + @overload + def __getitem__(self, key: SupportsIndex) -> TracebackEntry: ... + + @overload + def __getitem__(self, key: slice) -> Traceback: ... + + def __getitem__(self, key: SupportsIndex | slice) -> TracebackEntry | Traceback: + if isinstance(key, slice): + return self.__class__(super().__getitem__(key)) + else: + return super().__getitem__(key) + + def filter( + self, + excinfo_or_fn: ExceptionInfo[BaseException] | Callable[[TracebackEntry], bool], + /, + ) -> Traceback: + """Return a Traceback instance with certain items removed. + + If the filter is an `ExceptionInfo`, removes all the ``TracebackEntry``s + which are hidden (see ishidden() above). + + Otherwise, the filter is a function that gets a single argument, a + ``TracebackEntry`` instance, and should return True when the item should + be added to the ``Traceback``, False when not. + """ + if isinstance(excinfo_or_fn, ExceptionInfo): + fn = lambda x: not x.ishidden(excinfo_or_fn) # noqa: E731 + else: + fn = excinfo_or_fn + return Traceback(filter(fn, self)) + + def recursionindex(self) -> int | None: + """Return the index of the frame/TracebackEntry where recursion originates if + appropriate, None if no recursion occurred.""" + cache: dict[tuple[Any, int, int], list[dict[str, Any]]] = {} + for i, entry in enumerate(self): + # id for the code.raw is needed to work around + # the strange metaprogramming in the decorator lib from pypi + # which generates code objects that have hash/value equality + # XXX needs a test + key = entry.frame.code.path, id(entry.frame.code.raw), entry.lineno + values = cache.setdefault(key, []) + # Since Python 3.13 f_locals is a proxy, freeze it. + loc = dict(entry.frame.f_locals) + if values: + for otherloc in values: + if otherloc == loc: + return i + values.append(loc) + return None + + +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) + + +@final +@dataclasses.dataclass +class ExceptionInfo(Generic[E]): + """Wraps sys.exc_info() objects and offers help for navigating the traceback.""" + + _assert_start_repr: ClassVar = "AssertionError('assert " + + _excinfo: tuple[type[E], E, TracebackType] | None + _striptext: str + _traceback: Traceback | None + + def __init__( + self, + excinfo: tuple[type[E], E, TracebackType] | None, + striptext: str = "", + traceback: Traceback | None = None, + *, + _ispytest: bool = False, + ) -> None: + check_ispytest(_ispytest) + self._excinfo = excinfo + self._striptext = striptext + self._traceback = traceback + + @classmethod + def from_exception( + cls, + # Ignoring error: "Cannot use a covariant type variable as a parameter". + # This is OK to ignore because this class is (conceptually) readonly. + # See https://github.com/python/mypy/issues/7049. + exception: E, # type: ignore[misc] + exprinfo: str | None = None, + ) -> ExceptionInfo[E]: + """Return an ExceptionInfo for an existing exception. + + The exception must have a non-``None`` ``__traceback__`` attribute, + otherwise this function fails with an assertion error. This means that + the exception must have been raised, or added a traceback with the + :py:meth:`~BaseException.with_traceback()` method. + + :param exprinfo: + A text string helping to determine if we should strip + ``AssertionError`` from the output. Defaults to the exception + message/``__str__()``. + + .. versionadded:: 7.4 + """ + assert exception.__traceback__, ( + "Exceptions passed to ExcInfo.from_exception(...)" + " must have a non-None __traceback__." + ) + exc_info = (type(exception), exception, exception.__traceback__) + return cls.from_exc_info(exc_info, exprinfo) + + @classmethod + def from_exc_info( + cls, + exc_info: tuple[type[E], E, TracebackType], + exprinfo: str | None = None, + ) -> ExceptionInfo[E]: + """Like :func:`from_exception`, but using old-style exc_info tuple.""" + _striptext = "" + if exprinfo is None and isinstance(exc_info[1], AssertionError): + exprinfo = getattr(exc_info[1], "msg", None) + if exprinfo is None: + exprinfo = saferepr(exc_info[1]) + if exprinfo and exprinfo.startswith(cls._assert_start_repr): + _striptext = "AssertionError: " + + return cls(exc_info, _striptext, _ispytest=True) + + @classmethod + def from_current(cls, exprinfo: str | None = None) -> ExceptionInfo[BaseException]: + """Return an ExceptionInfo matching the current traceback. + + .. warning:: + + Experimental API + + :param exprinfo: + A text string helping to determine if we should strip + ``AssertionError`` from the output. Defaults to the exception + message/``__str__()``. + """ + tup = sys.exc_info() + assert tup[0] is not None, "no current exception" + assert tup[1] is not None, "no current exception" + assert tup[2] is not None, "no current exception" + exc_info = (tup[0], tup[1], tup[2]) + return ExceptionInfo.from_exc_info(exc_info, exprinfo) + + @classmethod + def for_later(cls) -> ExceptionInfo[E]: + """Return an unfilled ExceptionInfo.""" + return cls(None, _ispytest=True) + + def fill_unfilled(self, exc_info: tuple[type[E], E, TracebackType]) -> None: + """Fill an unfilled ExceptionInfo created with ``for_later()``.""" + assert self._excinfo is None, "ExceptionInfo was already filled" + self._excinfo = exc_info + + @property + def type(self) -> type[E]: + """The exception class.""" + assert self._excinfo is not None, ( + ".type can only be used after the context manager exits" + ) + return self._excinfo[0] + + @property + def value(self) -> E: + """The exception value.""" + assert self._excinfo is not None, ( + ".value can only be used after the context manager exits" + ) + return self._excinfo[1] + + @property + def tb(self) -> TracebackType: + """The exception raw traceback.""" + assert self._excinfo is not None, ( + ".tb can only be used after the context manager exits" + ) + return self._excinfo[2] + + @property + def typename(self) -> str: + """The type name of the exception.""" + assert self._excinfo is not None, ( + ".typename can only be used after the context manager exits" + ) + return self.type.__name__ + + @property + def traceback(self) -> Traceback: + """The traceback.""" + if self._traceback is None: + self._traceback = Traceback(self.tb) + return self._traceback + + @traceback.setter + def traceback(self, value: Traceback) -> None: + self._traceback = value + + def __repr__(self) -> str: + if self._excinfo is None: + return "" + return f"<{self.__class__.__name__} {saferepr(self._excinfo[1])} tblen={len(self.traceback)}>" + + def exconly(self, tryshort: bool = False) -> str: + """Return the exception as a string. + + When 'tryshort' resolves to True, and the exception is an + AssertionError, only the actual exception part of the exception + representation is returned (so 'AssertionError: ' is removed from + the beginning). + """ + + 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() + if tryshort: + if text.startswith(self._striptext): + text = text[len(self._striptext) :] + return text + + def errisinstance(self, exc: EXCEPTION_OR_MORE) -> bool: + """Return True if the exception is an instance of exc. + + Consider using ``isinstance(excinfo.value, exc)`` instead. + """ + return isinstance(self.value, exc) + + def _getreprcrash(self) -> ReprFileLocation | None: + # Find last non-hidden traceback entry that led to the exception of the + # traceback, or None if all hidden. + for i in range(-1, -len(self.traceback) - 1, -1): + entry = self.traceback[i] + if not entry.ishidden(self): + path, lineno = entry.frame.code.raw.co_filename, entry.lineno + exconly = self.exconly(tryshort=True) + return ReprFileLocation(path, lineno + 1, exconly) + return None + + def getrepr( + self, + showlocals: bool = False, + style: TracebackStyle = "long", + abspath: bool = False, + tbfilter: bool | Callable[[ExceptionInfo[BaseException]], Traceback] = True, + funcargs: bool = False, + truncate_locals: bool = True, + truncate_args: bool = True, + chain: bool = True, + ) -> ReprExceptionInfo | ExceptionChainRepr: + """Return str()able representation of this exception info. + + :param bool showlocals: + Show locals per traceback entry. + Ignored if ``style=="native"``. + + :param str style: + long|short|line|no|native|value traceback style. + + :param bool abspath: + If paths should be changed to absolute or left unchanged. + + :param tbfilter: + A filter for traceback entries. + + * If false, don't hide any entries. + * If true, hide internal entries and entries that contain a local + variable ``__tracebackhide__ = True``. + * If a callable, delegates the filtering to the callable. + + Ignored if ``style`` is ``"native"``. + + :param bool funcargs: + Show fixtures ("funcargs" for legacy purposes) per traceback entry. + + :param bool truncate_locals: + With ``showlocals==True``, make sure locals can be safely represented as strings. + + :param bool truncate_args: + With ``showargs==True``, make sure args can be safely represented as strings. + + :param bool chain: + If chained exceptions in Python 3 should be shown. + + .. versionchanged:: 3.9 + + Added the ``chain`` parameter. + """ + if style == "native": + return ReprExceptionInfo( + reprtraceback=ReprTracebackNative( + format_exception( + self.type, + self.value, + self.traceback[0]._rawentry if self.traceback else None, + ) + ), + reprcrash=self._getreprcrash(), + ) + + fmt = FormattedExcinfo( + showlocals=showlocals, + style=style, + abspath=abspath, + tbfilter=tbfilter, + funcargs=funcargs, + truncate_locals=truncate_locals, + truncate_args=truncate_args, + chain=chain, + ) + return fmt.repr_excinfo(self) + + def 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 = 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: + """Presenting information about failing Functions and Generators.""" + + # for traceback entries + flow_marker: ClassVar = ">" + fail_marker: ClassVar = "E" + + showlocals: bool = False + style: TracebackStyle = "long" + abspath: bool = True + tbfilter: TracebackFilter = True + funcargs: bool = False + truncate_locals: bool = True + truncate_args: bool = True + chain: bool = True + astcache: dict[str | Path, ast.AST] = dataclasses.field( + default_factory=dict, init=False, repr=False + ) + + def _getindent(self, source: Source) -> int: + # Figure out indent for the given source. + try: + s = str(source.getstatement(len(source) - 1)) + except KeyboardInterrupt: + raise + except BaseException: + try: + s = str(source[-1]) + except KeyboardInterrupt: + raise + except BaseException: + return 0 + return 4 + (len(s) - len(s.lstrip())) + + def _getentrysource(self, entry: TracebackEntry) -> Source | None: + source = entry.getsource(self.astcache) + if source is not None: + source = source.deindent() + return source + + def repr_args(self, entry: TracebackEntry) -> ReprFuncArgs | None: + if self.funcargs: + args = [] + for argname, argvalue in entry.frame.getargs(var=True): + if self.truncate_args: + str_repr = saferepr(argvalue) + else: + str_repr = saferepr(argvalue, maxsize=None) + args.append((argname, str_repr)) + return ReprFuncArgs(args) + return None + + def get_source( + self, + source: Source | None, + line_index: int = -1, + excinfo: ExceptionInfo[BaseException] | None = None, + short: bool = False, + 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: + line_index += len(source) + if source is None or line_index >= len(source.lines) or line_index < 0: + # `line_index` could still be outside `range(len(source.lines))` if + # we're processing AST with pathological position attributes. + source = Source("???") + line_index = 0 + space_prefix = " " + if short: + lines.append(space_prefix + source.lines[line_index].strip()) + 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: + indent = 4 if short else self._getindent(source) + 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]: + lines = [] + indentstr = " " * indent + # Get the real exception information out. + exlines = excinfo.exconly(tryshort=True).split("\n") + failindent = self.fail_marker + indentstr[1:] + for line in exlines: + lines.append(failindent + line) + if not markall: + failindent = indentstr + return lines + + def repr_locals(self, locals: Mapping[str, object]) -> ReprLocals | None: + if self.showlocals: + lines = [] + keys = [loc for loc in locals if loc[0] != "@"] + keys.sort() + for name in keys: + value = locals[name] + if name == "__builtins__": + lines.append("__builtins__ = ") + else: + # This formatting could all be handled by the + # _repr() function, which is only reprlib.Repr in + # disguise, so is very configurable. + if self.truncate_locals: + str_repr = saferepr(value) + else: + str_repr = safeformat(value) + # if len(str_repr) < 70 or not isinstance(value, (list, tuple, dict)): + lines.append(f"{name:<10} = {str_repr}") + # else: + # self._line("%-10s =\\" % (name,)) + # # XXX + # pprint.pprint(value, stream=self.excinfowriter) + return ReprLocals(lines) + return None + + def repr_traceback_entry( + self, + entry: TracebackEntry | None, + excinfo: ExceptionInfo[BaseException] | None = None, + ) -> ReprEntry: + lines: list[str] = [] + style = ( + entry._repr_style + if entry is not None and entry._repr_style is not None + else self.style + ) + if style in ("short", "long") and entry is not None: + source = self._getentrysource(entry) + if source is None: + source = Source("???") + line_index = 0 + end_line_index, colno, end_colno = None, None, None + else: + 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=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 = f"in {entry.name}" + else: + message = (excinfo and excinfo.typename) or "" + entry_path = entry.path + path = self._makepath(entry_path) + reprfileloc = ReprFileLocation(path, entry.lineno + 1, message) + localsrepr = self.repr_locals(entry.locals) + return ReprEntry(lines, reprargs, localsrepr, reprfileloc, style) + elif style == "value": + if excinfo: + lines.extend(str(excinfo.value).split("\n")) + return ReprEntry(lines, None, None, None, style) + else: + if excinfo: + lines.extend(self.get_exconly(excinfo, indent=4)) + return ReprEntry(lines, None, None, None, style) + + def _makepath(self, path: Path | str) -> str: + if not self.abspath and isinstance(path, Path): + try: + np = bestrelpath(Path.cwd(), path) + except OSError: + return str(path) + if len(np) < len(str(path)): + return np + return str(path) + + def repr_traceback(self, excinfo: ExceptionInfo[BaseException]) -> ReprTraceback: + traceback = filter_excinfo_traceback(self.tbfilter, excinfo) + + if isinstance(excinfo.value, RecursionError): + traceback, extraline = self._truncate_recursive_traceback(traceback) + else: + extraline = None + + if not traceback: + if extraline is None: + extraline = "All traceback entries are hidden. Pass `--full-trace` to see hidden and internal frames." + entries = [self.repr_traceback_entry(None, excinfo)] + return ReprTraceback(entries, extraline, style=self.style) + + last = traceback[-1] + if self.style == "value": + entries = [self.repr_traceback_entry(last, excinfo)] + return ReprTraceback(entries, None, style=self.style) + + entries = [ + self.repr_traceback_entry(entry, excinfo if last == entry else None) + for entry in traceback + ] + return ReprTraceback(entries, extraline, style=self.style) + + def _truncate_recursive_traceback( + self, traceback: Traceback + ) -> tuple[Traceback, str | None]: + """Truncate the given recursive traceback trying to find the starting + point of the recursion. + + The detection is done by going through each traceback entry and + finding the point in which the locals of the frame are equal to the + locals of a previous frame (see ``recursionindex()``). + + Handle the situation where the recursion process might raise an + exception (for example comparing numpy arrays using equality raises a + TypeError), in which case we do our best to warn the user of the + error and show a limited traceback. + """ + try: + recursionindex = traceback.recursionindex() + except Exception as e: + max_frames = 10 + extraline: str | None = ( + "!!! Recursion error detected, but an error occurred locating the origin of recursion.\n" + " The following exception happened when comparing locals in the stack frame:\n" + f" {type(e).__name__}: {e!s}\n" + f" Displaying first and last {max_frames} stack frames out of {len(traceback)}." + ) + # Type ignored because adding two instances of a List subtype + # currently incorrectly has type List instead of the subtype. + traceback = traceback[:max_frames] + traceback[-max_frames:] # type: ignore + else: + if recursionindex is not None: + extraline = "!!! Recursion detected (same locals & position)" + traceback = traceback[: recursionindex + 1] + else: + extraline = None + + return traceback, extraline + + def repr_excinfo(self, excinfo: ExceptionInfo[BaseException]) -> ExceptionChainRepr: + repr_chain: list[tuple[ReprTraceback, ReprFileLocation | None, str | None]] = [] + e: BaseException | None = excinfo.value + excinfo_: ExceptionInfo[BaseException] | None = excinfo + descr = None + seen: set[int] = set() + while e is not None and id(e) not in seen: + seen.add(id(e)) + + if excinfo_: + # Fall back to native traceback as a temporary workaround until + # full support for exception groups added to ExceptionInfo. + # See https://github.com/pytest-dev/pytest/issues/9159 + reprtraceback: ReprTraceback | ReprTracebackNative + if isinstance(e, BaseExceptionGroup): + # 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: + reprtraceback = self.repr_traceback(excinfo_) + reprcrash = excinfo_._getreprcrash() + else: + # Fallback to native repr if the exception doesn't have a traceback: + # ExceptionInfo objects require a full traceback to work. + reprtraceback = ReprTracebackNative(format_exception(type(e), e, None)) + reprcrash = None + repr_chain += [(reprtraceback, reprcrash, descr)] + + if e.__cause__ is not None and self.chain: + e = e.__cause__ + excinfo_ = ExceptionInfo.from_exception(e) if e.__traceback__ else None + descr = "The above exception was the direct cause of the following exception:" + elif ( + e.__context__ is not None and not e.__suppress_context__ and self.chain + ): + e = e.__context__ + excinfo_ = ExceptionInfo.from_exception(e) if e.__traceback__ else None + descr = "During handling of the above exception, another exception occurred:" + else: + e = None + repr_chain.reverse() + return ExceptionChainRepr(repr_chain) + + +@dataclasses.dataclass(eq=False) +class TerminalRepr: + def __str__(self) -> str: + # FYI this is called from pytest-xdist's serialization of exception + # information. + io = StringIO() + tw = TerminalWriter(file=io) + self.toterminal(tw) + return io.getvalue().strip() + + def __repr__(self) -> str: + return f"<{self.__class__} instance at {id(self):0x}>" + + def toterminal(self, tw: TerminalWriter) -> None: + raise NotImplementedError() + + +# This class is abstract -- only subclasses are instantiated. +@dataclasses.dataclass(eq=False) +class ExceptionRepr(TerminalRepr): + # Provided by subclasses. + reprtraceback: ReprTraceback + reprcrash: ReprFileLocation | None + sections: list[tuple[str, str, str]] = dataclasses.field( + init=False, default_factory=list + ) + + def addsection(self, name: str, content: str, sep: str = "-") -> None: + self.sections.append((name, content, sep)) + + def toterminal(self, tw: TerminalWriter) -> None: + for name, content, sep in self.sections: + tw.sep(sep, name) + tw.line(content) + + +@dataclasses.dataclass(eq=False) +class ExceptionChainRepr(ExceptionRepr): + chain: Sequence[tuple[ReprTraceback, ReprFileLocation | None, str | None]] + + def __init__( + self, + chain: Sequence[tuple[ReprTraceback, ReprFileLocation | None, str | None]], + ) -> None: + # reprcrash and reprtraceback of the outermost (the newest) exception + # in the chain. + super().__init__( + reprtraceback=chain[-1][0], + reprcrash=chain[-1][1], + ) + self.chain = chain + + def toterminal(self, tw: TerminalWriter) -> None: + for element in self.chain: + element[0].toterminal(tw) + if element[2] is not None: + tw.line("") + tw.line(element[2], yellow=True) + super().toterminal(tw) + + +@dataclasses.dataclass(eq=False) +class ReprExceptionInfo(ExceptionRepr): + reprtraceback: ReprTraceback + reprcrash: ReprFileLocation | None + + def toterminal(self, tw: TerminalWriter) -> None: + self.reprtraceback.toterminal(tw) + super().toterminal(tw) + + +@dataclasses.dataclass(eq=False) +class ReprTraceback(TerminalRepr): + reprentries: Sequence[ReprEntry | ReprEntryNative] + extraline: str | None + style: TracebackStyle + + entrysep: ClassVar = "_ " + + def toterminal(self, tw: TerminalWriter) -> None: + # The entries might have different styles. + for i, entry in enumerate(self.reprentries): + if entry.style == "long": + tw.line("") + entry.toterminal(tw) + if i < len(self.reprentries) - 1: + next_entry = self.reprentries[i + 1] + if entry.style == "long" or ( + entry.style == "short" and next_entry.style == "long" + ): + tw.sep(self.entrysep) + + if self.extraline: + tw.line(self.extraline) + + +class ReprTracebackNative(ReprTraceback): + def __init__(self, tblines: Sequence[str]) -> None: + self.reprentries = [ReprEntryNative(tblines)] + self.extraline = None + self.style = "native" + + +@dataclasses.dataclass(eq=False) +class ReprEntryNative(TerminalRepr): + lines: Sequence[str] + + style: ClassVar[TracebackStyle] = "native" + + def toterminal(self, tw: TerminalWriter) -> None: + tw.write("".join(self.lines)) + + +@dataclasses.dataclass(eq=False) +class ReprEntry(TerminalRepr): + lines: Sequence[str] + reprfuncargs: ReprFuncArgs | None + reprlocals: ReprLocals | None + reprfileloc: ReprFileLocation | None + style: TracebackStyle + + def _write_entry_lines(self, tw: TerminalWriter) -> None: + """Write the source code portions of a list of traceback entries with syntax highlighting. + + Usually entries are lines like these: + + " x = 1" + "> assert x == 2" + "E assert 1 == 2" + + This function takes care of rendering the "source" portions of it (the lines without + the "E" prefix) using syntax highlighting, taking care to not highlighting the ">" + character, as doing so might break line continuations. + """ + if not self.lines: + return + + if self.style == "value": + # Using tw.write instead of tw.line for testing purposes due to TWMock implementation; + # lines written with TWMock.line and TWMock._write_source cannot be distinguished + # from each other, whereas lines written with TWMock.write are marked with TWMock.WRITE + for line in self.lines: + tw.write(line) + tw.write("\n") + return + + # separate indents and source lines that are not failures: we want to + # highlight the code but not the indentation, which may contain markers + # such as "> assert 0" + fail_marker = f"{FormattedExcinfo.fail_marker} " + indent_size = len(fail_marker) + indents: list[str] = [] + source_lines: list[str] = [] + failure_lines: list[str] = [] + for index, line in enumerate(self.lines): + is_failure_line = line.startswith(fail_marker) + if is_failure_line: + # from this point on all lines are considered part of the failure + failure_lines.extend(self.lines[index:]) + break + else: + indents.append(line[:indent_size]) + source_lines.append(line[indent_size:]) + + tw._write_source(source_lines, indents) + + # failure lines are always completely red and bold + for line in failure_lines: + tw.line(line, bold=True, red=True) + + def toterminal(self, tw: TerminalWriter) -> None: + if self.style == "short": + if self.reprfileloc: + self.reprfileloc.toterminal(tw) + self._write_entry_lines(tw) + if self.reprlocals: + self.reprlocals.toterminal(tw, indent=" " * 8) + return + + if self.reprfuncargs: + self.reprfuncargs.toterminal(tw) + + self._write_entry_lines(tw) + + if self.reprlocals: + tw.line("") + self.reprlocals.toterminal(tw) + if self.reprfileloc: + if self.lines: + tw.line("") + self.reprfileloc.toterminal(tw) + + def __str__(self) -> str: + return "{}\n{}\n{}".format( + "\n".join(self.lines), self.reprlocals, self.reprfileloc + ) + + +@dataclasses.dataclass(eq=False) +class ReprFileLocation(TerminalRepr): + path: str + lineno: int + message: str + + def __post_init__(self) -> None: + self.path = str(self.path) + + def toterminal(self, tw: TerminalWriter) -> None: + # Filename and lineno output for each entry, using an output format + # that most editors understand. + msg = self.message + i = msg.find("\n") + if i != -1: + msg = msg[:i] + tw.write(self.path, bold=True, red=True) + tw.line(f":{self.lineno}: {msg}") + + +@dataclasses.dataclass(eq=False) +class ReprLocals(TerminalRepr): + lines: Sequence[str] + + def toterminal(self, tw: TerminalWriter, indent="") -> None: + for line in self.lines: + tw.line(indent + line) + + +@dataclasses.dataclass(eq=False) +class ReprFuncArgs(TerminalRepr): + args: Sequence[tuple[str, object]] + + def toterminal(self, tw: TerminalWriter) -> None: + if self.args: + linesofar = "" + for name, value in self.args: + ns = f"{name} = {value}" + if len(ns) + len(linesofar) + 2 > tw.fullwidth: + if linesofar: + tw.line(linesofar) + linesofar = ns + else: + if linesofar: + linesofar += ", " + ns + else: + linesofar = ns + if linesofar: + tw.line(linesofar) + tw.line("") + + +def getfslineno(obj: object) -> tuple[str | Path, int]: + """Return source location (path, lineno) for the given object. + + If the source cannot be determined return ("", -1). + + The line number is 0-based. + """ + # xxx let decorators etc specify a sane ordering + # NOTE: this used to be done in _pytest.compat.getfslineno, initially added + # in 6ec13a2b9. It ("place_as") appears to be something very custom. + obj = get_real_func(obj) + if hasattr(obj, "place_as"): + obj = obj.place_as + + try: + code = Code.from_function(obj) + except TypeError: + try: + fn = inspect.getsourcefile(obj) or inspect.getfile(obj) # type: ignore[arg-type] + except TypeError: + return "", -1 + + fspath = (fn and absolutepath(fn)) or "" + lineno = -1 + if fspath: + try: + _, lineno = findsource(obj) + except OSError: + pass + return fspath, lineno + + return code.path, code.firstlineno + + +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 +# for better maintenance. + +_PLUGGY_DIR = Path(pluggy.__file__.rstrip("oc")) +# pluggy is either a package or a single module depending on the version +if _PLUGGY_DIR.name == "__init__.py": + _PLUGGY_DIR = _PLUGGY_DIR.parent +_PYTEST_DIR = Path(_pytest.__file__).parent + + +def filter_traceback(entry: TracebackEntry) -> bool: + """Return True if a TracebackEntry instance should be included in tracebacks. + + We hide traceback entries of: + + * dynamically generated code (no code to show up for it); + * internal traceback from pytest or its internal libraries, py and pluggy. + """ + # entry.path might sometimes return a str object when the entry + # points to dynamically generated code. + # See https://bitbucket.org/pytest-dev/py/issues/71. + raw_filename = entry.frame.code.raw.co_filename + is_generated = "<" in raw_filename and ">" in raw_filename + if is_generated: + return False + + # entry.path might point to a non-existing file, in which case it will + # also return a str object. See #1133. + p = Path(entry.path) + + parents = p.parents + if _PLUGGY_DIR in parents: + return False + if _PYTEST_DIR in parents: + return False + + return True + + +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/py311/lib/python3.11/site-packages/_pytest/_code/source.py b/py311/lib/python3.11/site-packages/_pytest/_code/source.py new file mode 100644 index 0000000000000000000000000000000000000000..99c242dd98e27ad6dcdf25a2c7e68e6fad209dd6 --- /dev/null +++ b/py311/lib/python3.11/site-packages/_pytest/_code/source.py @@ -0,0 +1,225 @@ +# 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 +from typing import overload +import warnings + + +class Source: + """An immutable object holding a source code fragment. + + When using Source(...), the source lines are deindented. + """ + + def __init__(self, obj: object = None) -> None: + if not obj: + self.lines: list[str] = [] + self.raw_lines: list[str] = [] + elif isinstance(obj, Source): + self.lines = obj.lines + 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) + src = inspect.getsource(rawcode) + 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): + return NotImplemented + return self.lines == other.lines + + # Ignore type because of https://github.com/python/mypy/issues/4266. + __hash__ = None # type: ignore + + @overload + def __getitem__(self, key: int) -> str: ... + + @overload + def __getitem__(self, key: slice) -> Source: ... + + def __getitem__(self, key: int | slice) -> str | Source: + if isinstance(key, int): + return self.lines[key] + else: + if key.step not in (None, 1): + raise IndexError("cannot slice a Source with a step") + newsource = Source() + newsource.lines = self.lines[key.start : key.stop] + newsource.raw_lines = self.raw_lines[key.start : key.stop] + return newsource + + def __iter__(self) -> Iterator[str]: + return iter(self.lines) + + def __len__(self) -> int: + return len(self.lines) + + def strip(self) -> Source: + """Return new Source object with trailing and leading blank lines removed.""" + start, end = 0, len(self) + while start < end and not self.lines[start].strip(): + start += 1 + while end > start and not self.lines[end - 1].strip(): + end -= 1 + source = Source() + source.raw_lines = self.raw_lines + source.lines[:] = self.lines[start:end] + return source + + def indent(self, indent: str = " " * 4) -> Source: + """Return a copy of the source object with all lines indented by the + given indent-string.""" + newsource = Source() + newsource.raw_lines = self.raw_lines + newsource.lines = [(indent + line) for line in self.lines] + return newsource + + def getstatement(self, lineno: int) -> Source: + """Return Source statement which contains the given linenumber + (counted from 0).""" + start, end = self.getstatementrange(lineno) + return self[start:end] + + def getstatementrange(self, lineno: int) -> tuple[int, int]: + """Return (start, end) tuple which spans the minimal statement region + which containing the given lineno.""" + if not (0 <= lineno < len(self)): + raise IndexError("lineno out of range") + _ast, start, end = getstatementrange_ast(lineno, self) + return start, end + + def deindent(self) -> Source: + """Return a new Source object deindented.""" + newsource = Source() + newsource.lines[:] = deindent(self.lines) + newsource.raw_lines = self.raw_lines + return newsource + + def __str__(self) -> str: + return "\n".join(self.lines) + + +# +# helper functions +# + + +def findsource(obj) -> tuple[Source | None, int]: + try: + sourcelines, lineno = inspect.findsource(obj) + except Exception: + return None, -1 + source = Source() + source.lines = [line.rstrip() for line in sourcelines] + source.raw_lines = sourcelines + return source, lineno + + +def getrawcode(obj: object, trycall: bool = True) -> types.CodeType: + """Return code object for given function.""" + try: + return obj.__code__ # type: ignore[attr-defined,no-any-return] + except AttributeError: + pass + if trycall: + call = getattr(obj, "__call__", None) + if call and not isinstance(obj, type): + return getrawcode(call, trycall=False) + raise TypeError(f"could not get code object for {obj!r}") + + +def deindent(lines: Iterable[str]) -> list[str]: + return textwrap.dedent("\n".join(lines)).splitlines() + + +def get_statement_startend2(lineno: int, node: ast.AST) -> tuple[int, int | None]: + # Flatten all statements and except handlers into one lineno-list. + # AST's line numbers start indexing at 1. + values: list[int] = [] + for x in ast.walk(node): + if isinstance(x, ast.stmt | ast.ExceptHandler): + # The lineno points to the class/def, so need to include the decorators. + if isinstance(x, ast.ClassDef | ast.FunctionDef | ast.AsyncFunctionDef): + for d in x.decorator_list: + values.append(d.lineno - 1) + values.append(x.lineno - 1) + for name in ("finalbody", "orelse"): + val: list[ast.stmt] | None = getattr(x, name, None) + if val: + # Treat the finally/orelse part as its own statement. + values.append(val[0].lineno - 1 - 1) + values.sort() + insert_index = bisect_right(values, lineno) + start = values[insert_index - 1] + if insert_index >= len(values): + end = None + else: + end = values[insert_index] + return start, end + + +def getstatementrange_ast( + lineno: int, + source: Source, + assertion: bool = False, + astnode: ast.AST | None = None, +) -> tuple[ast.AST, int, int]: + if astnode is None: + content = str(source) + # See #4260: + # Don't produce duplicate warnings when compiling source to find AST. + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + astnode = ast.parse(content, "source", "exec") + + start, end = get_statement_startend2(lineno, astnode) + # We need to correct the end: + # - ast-parsing strips comments + # - there might be empty lines + # - we might have lesser indented code blocks at the end + if end is None: + end = len(source.lines) + + if end > start + 1: + # Make sure we don't span differently indented code blocks + # by using the BlockFinder helper used which inspect.getsource() uses itself. + block_finder = inspect.BlockFinder() + # If we start with an indented line, put blockfinder to "started" mode. + block_finder.started = ( + bool(source.lines[start]) and source.lines[start][0].isspace() + ) + it = ((x + "\n") for x in source.lines[start:end]) + try: + for tok in tokenize.generate_tokens(lambda: next(it)): + block_finder.tokeneater(*tok) + except (inspect.EndOfBlock, IndentationError): + end = block_finder.last + start + except Exception: + pass + + # The end might still point to a comment or empty line, correct it. + while end: + line = source.lines[end - 1].lstrip() + if line.startswith("#") or not line: + end -= 1 + else: + break + return astnode, start, end diff --git a/py311/lib/python3.11/site-packages/_pytest/_io/__init__.py b/py311/lib/python3.11/site-packages/_pytest/_io/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..b0155b18b605326ba0a3104deaefde938b7d651a --- /dev/null +++ b/py311/lib/python3.11/site-packages/_pytest/_io/__init__.py @@ -0,0 +1,10 @@ +from __future__ import annotations + +from .terminalwriter import get_terminal_width +from .terminalwriter import TerminalWriter + + +__all__ = [ + "TerminalWriter", + "get_terminal_width", +] diff --git a/py311/lib/python3.11/site-packages/_pytest/_io/pprint.py b/py311/lib/python3.11/site-packages/_pytest/_io/pprint.py new file mode 100644 index 0000000000000000000000000000000000000000..28f069092061928a1c06aaba94b6e8ba4f03075f --- /dev/null +++ b/py311/lib/python3.11/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/py311/lib/python3.11/site-packages/_pytest/_io/saferepr.py b/py311/lib/python3.11/site-packages/_pytest/_io/saferepr.py new file mode 100644 index 0000000000000000000000000000000000000000..cee70e332f9802a5963bfed8149ac997e5b30de2 --- /dev/null +++ b/py311/lib/python3.11/site-packages/_pytest/_io/saferepr.py @@ -0,0 +1,130 @@ +from __future__ import annotations + +import pprint +import reprlib + + +def _try_repr_or_str(obj: object) -> str: + try: + return repr(obj) + except (KeyboardInterrupt, SystemExit): + raise + except BaseException: + return f'{type(obj).__name__}("{obj}")' + + +def _format_repr_exception(exc: BaseException, obj: object) -> str: + try: + exc_info = _try_repr_or_str(exc) + except (KeyboardInterrupt, SystemExit): + raise + except BaseException as inner_exc: + exc_info = f"unpresentable exception ({_try_repr_or_str(inner_exc)})" + return ( + f"<[{exc_info} raised in repr()] {type(obj).__name__} object at 0x{id(obj):x}>" + ) + + +def _ellipsize(s: str, maxsize: int) -> str: + if len(s) > maxsize: + i = max(0, (maxsize - 3) // 2) + j = max(0, maxsize - 3 - i) + return s[:i] + "..." + s[len(s) - j :] + return s + + +class SafeRepr(reprlib.Repr): + """ + repr.Repr that limits the resulting size of repr() and includes + information on exceptions raised during the call. + """ + + def __init__(self, maxsize: int | None, use_ascii: bool = False) -> None: + """ + :param maxsize: + If not None, will truncate the resulting repr to that specific size, using ellipsis + somewhere in the middle to hide the extra text. + If None, will not impose any size limits on the returning repr. + """ + super().__init__() + # ``maxstring`` is used by the superclass, and needs to be an int; using a + # very large number in case maxsize is None, meaning we want to disable + # truncation. + self.maxstring = maxsize if maxsize is not None else 1_000_000_000 + self.maxsize = maxsize + self.use_ascii = use_ascii + + def repr(self, x: object) -> str: + try: + if self.use_ascii: + s = ascii(x) + else: + s = super().repr(x) + except (KeyboardInterrupt, SystemExit): + raise + except BaseException as exc: + s = _format_repr_exception(exc, x) + if self.maxsize is not None: + s = _ellipsize(s, self.maxsize) + return s + + def repr_instance(self, x: object, level: int) -> str: + try: + s = repr(x) + except (KeyboardInterrupt, SystemExit): + raise + except BaseException as exc: + s = _format_repr_exception(exc, x) + if self.maxsize is not None: + s = _ellipsize(s, self.maxsize) + return s + + +def safeformat(obj: object) -> str: + """Return a pretty printed string for the given object. + + Failing __repr__ functions of user instances will be represented + with a short exception info. + """ + try: + return pprint.pformat(obj) + except Exception as exc: + return _format_repr_exception(exc, obj) + + +# Maximum size of overall repr of objects to display during assertion errors. +DEFAULT_REPR_MAX_SIZE = 240 + + +def saferepr( + obj: object, maxsize: int | None = DEFAULT_REPR_MAX_SIZE, use_ascii: bool = False +) -> str: + """Return a size-limited safe repr-string for the given object. + + Failing __repr__ functions of user instances will be represented + with a short exception info and 'saferepr' generally takes + care to never raise exceptions itself. + + This function is a wrapper around the Repr/reprlib functionality of the + stdlib. + """ + return SafeRepr(maxsize, use_ascii).repr(obj) + + +def saferepr_unlimited(obj: object, use_ascii: bool = True) -> str: + """Return an unlimited-size safe repr-string for the given object. + + As with saferepr, failing __repr__ functions of user instances + will be represented with a short exception info. + + This function is a wrapper around simple repr. + + Note: a cleaner solution would be to alter ``saferepr``this way + when maxsize=None, but that might affect some other code. + """ + try: + if use_ascii: + return ascii(obj) + return repr(obj) + except Exception as exc: + return _format_repr_exception(exc, obj) diff --git a/py311/lib/python3.11/site-packages/_pytest/_io/terminalwriter.py b/py311/lib/python3.11/site-packages/_pytest/_io/terminalwriter.py new file mode 100644 index 0000000000000000000000000000000000000000..9191b4edace06d673febe36b708fdefd53bae9be --- /dev/null +++ b/py311/lib/python3.11/site-packages/_pytest/_io/terminalwriter.py @@ -0,0 +1,258 @@ +"""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 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 + + +# This code was initially copied from py 1.8.1, file _io/terminalwriter.py. + + +def get_terminal_width() -> int: + width, _ = shutil.get_terminal_size(fallback=(80, 24)) + + # The Windows get_terminal_size may be bogus, let's sanify a bit. + if width < 40: + width = 80 + + return width + + +def should_do_markup(file: TextIO) -> bool: + if os.environ.get("PY_COLORS") == "1": + return True + if os.environ.get("PY_COLORS") == "0": + return False + if os.environ.get("NO_COLOR"): + return False + if os.environ.get("FORCE_COLOR"): + return True + return ( + hasattr(file, "isatty") and file.isatty() and os.environ.get("TERM") != "dumb" + ) + + +@final +class TerminalWriter: + _esctable = dict( + black=30, + red=31, + green=32, + yellow=33, + blue=34, + purple=35, + cyan=36, + white=37, + Black=40, + Red=41, + Green=42, + Yellow=43, + Blue=44, + Purple=45, + Cyan=46, + White=47, + bold=1, + light=2, + blink=5, + invert=7, + ) + + def __init__(self, file: TextIO | None = None) -> None: + if file is None: + file = sys.stdout + if hasattr(file, "isatty") and file.isatty() and sys.platform == "win32": + try: + import colorama + except ImportError: + pass + else: + file = colorama.AnsiToWin32(file).stream + assert file is not None + self._file = file + self.hasmarkup = should_do_markup(file) + self._current_line = "" + self._terminal_width: int | None = None + self.code_highlight = True + + @property + def fullwidth(self) -> int: + if self._terminal_width is not None: + return self._terminal_width + return get_terminal_width() + + @fullwidth.setter + def fullwidth(self, value: int) -> None: + self._terminal_width = value + + @property + def width_of_current_line(self) -> int: + """Return an estimate of the width so far in the current line.""" + return wcswidth(self._current_line) + + def markup(self, text: str, **markup: bool) -> str: + for name in markup: + if name not in self._esctable: + raise ValueError(f"unknown markup: {name!r}") + if self.hasmarkup: + esc = [self._esctable[name] for name, on in markup.items() if on] + if esc: + text = "".join(f"\x1b[{cod}m" for cod in esc) + text + "\x1b[0m" + return text + + def sep( + self, + sepchar: str, + title: str | None = None, + fullwidth: int | None = None, + **markup: bool, + ) -> None: + if fullwidth is None: + fullwidth = self.fullwidth + # The goal is to have the line be as long as possible + # under the condition that len(line) <= fullwidth. + if sys.platform == "win32": + # If we print in the last column on windows we are on a + # new line but there is no way to verify/neutralize this + # (we may not know the exact line width). + # So let's be defensive to avoid empty lines in the output. + fullwidth -= 1 + if title is not None: + # we want 2 + 2*len(fill) + len(title) <= fullwidth + # i.e. 2 + 2*len(sepchar)*N + len(title) <= fullwidth + # 2*len(sepchar)*N <= fullwidth - len(title) - 2 + # N <= (fullwidth - len(title) - 2) // (2*len(sepchar)) + N = max((fullwidth - len(title) - 2) // (2 * len(sepchar)), 1) + fill = sepchar * N + line = f"{fill} {title} {fill}" + else: + # we want len(sepchar)*N <= fullwidth + # i.e. N <= fullwidth // len(sepchar) + line = sepchar * (fullwidth // len(sepchar)) + # In some situations there is room for an extra sepchar at the right, + # in particular if we consider that with a sepchar like "_ " the + # trailing space is not important at the end of the line. + if len(line) + len(sepchar.rstrip()) <= fullwidth: + line += sepchar.rstrip() + + self.line(line, **markup) + + def write(self, msg: str, *, flush: bool = False, **markup: bool) -> None: + if msg: + current_line = msg.rsplit("\n", 1)[-1] + if "\n" in msg: + self._current_line = current_line + else: + self._current_line += current_line + + msg = self.markup(msg, **markup) + + self.write_raw(msg, flush=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) + self.write("\n") + + def flush(self) -> None: + self._file.flush() + + def _write_source(self, lines: Sequence[str], indents: Sequence[str] = ()) -> None: + """Write lines of source code possibly highlighted. + + Keeping this private for now because the API is clunky. We should discuss how + to evolve the terminal writer so we can have more precise color support, for example + being able to write part of a line in one color and the rest in another, and so on. + """ + if indents and len(indents) != len(lines): + raise ValueError( + f"indents size ({len(indents)}) should have same size as lines ({len(lines)})" + ) + if not indents: + indents = [""] * len(lines) + source = "\n".join(lines) + new_lines = self._highlight(source).splitlines() + # 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 _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 + + theme = os.getenv("PYTEST_THEME") + theme_mode = os.getenv("PYTEST_THEME_MODE", "dark") + + try: + return TerminalFormatter(bg=theme_mode, style=theme) + except pygments.util.ClassNotFound as e: + raise UsageError( + f"PYTEST_THEME environment variable has an invalid value: '{theme}'. " + "Hint: See available pygments styles with `pygmentize -L styles`." + ) from e + except pygments.util.OptionError as e: + raise UsageError( + f"PYTEST_THEME_MODE environment variable has an invalid value: '{theme_mode}'. " + "The allowed values are 'dark' (default) and 'light'." + ) from e + + def _highlight( + self, source: str, lexer: Literal["diff", "python"] = "python" + ) -> str: + """Highlight the given source if we have markup support.""" + if not source or not self.hasmarkup or not self.code_highlight: + return source + + pygments_lexer = self._get_pygments_lexer(lexer) + 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/py311/lib/python3.11/site-packages/_pytest/_io/wcwidth.py b/py311/lib/python3.11/site-packages/_pytest/_io/wcwidth.py new file mode 100644 index 0000000000000000000000000000000000000000..23886ff1581a16aa97e5c375e62261622e24c169 --- /dev/null +++ b/py311/lib/python3.11/site-packages/_pytest/_io/wcwidth.py @@ -0,0 +1,57 @@ +from __future__ import annotations + +from functools import lru_cache +import unicodedata + + +@lru_cache(100) +def wcwidth(c: str) -> int: + """Determine how many columns are needed to display a character in a terminal. + + Returns -1 if the character is not printable. + Returns 0, 1 or 2 for other characters. + """ + o = ord(c) + + # ASCII fast path. + if 0x20 <= o < 0x07F: + return 1 + + # Some Cf/Zp/Zl characters which should be zero-width. + if ( + o == 0x0000 + or 0x200B <= o <= 0x200F + or 0x2028 <= o <= 0x202E + or 0x2060 <= o <= 0x2063 + ): + return 0 + + category = unicodedata.category(c) + + # Control characters. + if category == "Cc": + return -1 + + # Combining characters with zero width. + if category in ("Me", "Mn"): + return 0 + + # Full/Wide east asian characters. + if unicodedata.east_asian_width(c) in ("F", "W"): + return 2 + + return 1 + + +def wcswidth(s: str) -> int: + """Determine how many columns are needed to display a string in a terminal. + + Returns -1 if the string contains non-printable characters. + """ + width = 0 + for c in unicodedata.normalize("NFC", s): + wc = wcwidth(c) + if wc < 0: + return -1 + width += wc + return width diff --git a/py311/lib/python3.11/site-packages/_pytest/_py/__init__.py b/py311/lib/python3.11/site-packages/_pytest/_py/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/py311/lib/python3.11/site-packages/_pytest/_py/error.py b/py311/lib/python3.11/site-packages/_pytest/_py/error.py new file mode 100644 index 0000000000000000000000000000000000000000..dace23764ffb4da9744cf23b668c9e7011674c67 --- /dev/null +++ b/py311/lib/python3.11/site-packages/_pytest/_py/error.py @@ -0,0 +1,119 @@ +"""create errno-specific classes for IO or os calls.""" + +from __future__ import annotations + +from collections.abc import Callable +import errno +import os +import sys +from typing import TYPE_CHECKING +from typing import TypeVar + + +if TYPE_CHECKING: + from typing_extensions import ParamSpec + + P = ParamSpec("P") + +R = TypeVar("R") + + +class Error(EnvironmentError): + def __repr__(self) -> str: + return "{}.{} {!r}: {} ".format( + self.__class__.__module__, + self.__class__.__name__, + self.__class__.__doc__, + " ".join(map(str, self.args)), + # repr(self.args) + ) + + def __str__(self) -> str: + s = "[{}]: {}".format( + self.__class__.__doc__, + " ".join(map(str, self.args)), + ) + return s + + +_winerrnomap = { + 2: errno.ENOENT, + 3: errno.ENOENT, + 17: errno.EEXIST, + 18: errno.EXDEV, + 13: errno.EBUSY, # empty cd drive, but ENOMEDIUM seems unavailable + 22: errno.ENOTDIR, + 20: errno.ENOTDIR, + 267: errno.ENOTDIR, + 5: errno.EACCES, # anything better? +} + + +class ErrorMaker: + """lazily provides Exception classes for each possible POSIX errno + (as defined per the 'errno' module). All such instances + subclass EnvironmentError. + """ + + _errno2class: dict[int, type[Error]] = {} + + def __getattr__(self, name: str) -> type[Error]: + if name[0] == "_": + raise AttributeError(name) + eno = getattr(errno, name) + cls = self._geterrnoclass(eno) + setattr(self, name, cls) + return cls + + def _geterrnoclass(self, eno: int) -> type[Error]: + try: + return self._errno2class[eno] + except KeyError: + clsname = errno.errorcode.get(eno, f"UnknownErrno{eno}") + errorcls = type( + clsname, + (Error,), + {"__module__": "py.error", "__doc__": os.strerror(eno)}, + ) + self._errno2class[eno] = errorcls + return errorcls + + def checked_call( + self, func: Callable[P, R], *args: P.args, **kwargs: P.kwargs + ) -> R: + """Call a function and raise an errno-exception if applicable.""" + __tracebackhide__ = True + try: + return func(*args, **kwargs) + except Error: + raise + except OSError as value: + if not hasattr(value, "errno"): + raise + if sys.platform == "win32": + try: + # error: Invalid index type "Optional[int]" for "dict[int, int]"; expected type "int" [index] + # OK to ignore because we catch the KeyError below. + cls = self._geterrnoclass(_winerrnomap[value.errno]) # type:ignore[index] + except KeyError: + raise value + else: + # we are not on Windows, or we got a proper OSError + if value.errno is None: + cls = type( + "UnknownErrnoNone", + (Error,), + {"__module__": "py.error", "__doc__": None}, + ) + else: + cls = self._geterrnoclass(value.errno) + + raise cls(f"{func.__name__}{args!r}") + + +_error_maker = ErrorMaker() +checked_call = _error_maker.checked_call + + +def __getattr__(attr: str) -> type[Error]: + return getattr(_error_maker, attr) # type: ignore[no-any-return] diff --git a/py311/lib/python3.11/site-packages/_pytest/_py/path.py b/py311/lib/python3.11/site-packages/_pytest/_py/path.py new file mode 100644 index 0000000000000000000000000000000000000000..b7131b08a209b7d7934e4621479ef384318d21a6 --- /dev/null +++ b/py311/lib/python3.11/site-packages/_pytest/_py/path.py @@ -0,0 +1,1475 @@ +# mypy: allow-untyped-defs +"""local path implementation.""" + +from __future__ import annotations + +import atexit +from collections.abc import Callable +from contextlib import contextmanager +import fnmatch +import importlib.util +import io +import os +from os.path import abspath +from os.path import dirname +from os.path import exists +from os.path import isabs +from os.path import isdir +from os.path import isfile +from os.path import islink +from os.path import normpath +import posixpath +from stat import S_ISDIR +from stat import S_ISLNK +from stat import S_ISREG +import sys +from typing import Any +from typing import cast +from typing import Literal +from typing import overload +from typing import TYPE_CHECKING +import uuid +import warnings + +from . import error + + +# Moved from local.py. +iswin32 = sys.platform == "win32" or (getattr(os, "_name", False) == "nt") + + +class Checkers: + _depend_on_existence = "exists", "link", "dir", "file" + + def __init__(self, path): + self.path = path + + def dotfile(self): + return self.path.basename.startswith(".") + + def ext(self, arg): + if not arg.startswith("."): + arg = "." + arg + return self.path.ext == arg + + def basename(self, arg): + return self.path.basename == arg + + def basestarts(self, arg): + return self.path.basename.startswith(arg) + + def relto(self, arg): + return self.path.relto(arg) + + def fnmatch(self, arg): + return self.path.fnmatch(arg) + + def endswith(self, arg): + return str(self.path).endswith(arg) + + def _evaluate(self, kw): + from .._code.source import getrawcode + + for name, value in kw.items(): + invert = False + meth = None + try: + meth = getattr(self, name) + except AttributeError: + if name[:3] == "not": + invert = True + try: + meth = getattr(self, name[3:]) + except AttributeError: + pass + if meth is None: + raise TypeError(f"no {name!r} checker available for {self.path!r}") + try: + if getrawcode(meth).co_argcount > 1: + if (not meth(value)) ^ invert: + return False + else: + if bool(value) ^ bool(meth()) ^ invert: + return False + except (error.ENOENT, error.ENOTDIR, error.EBUSY): + # EBUSY feels not entirely correct, + # but its kind of necessary since ENOMEDIUM + # is not accessible in python + for name in self._depend_on_existence: + if name in kw: + if kw.get(name): + return False + name = "not" + name + if name in kw: + if not kw.get(name): + return False + return True + + _statcache: Stat + + def _stat(self) -> Stat: + try: + return self._statcache + except AttributeError: + try: + self._statcache = self.path.stat() + except error.ELOOP: + self._statcache = self.path.lstat() + return self._statcache + + def dir(self): + return S_ISDIR(self._stat().mode) + + def file(self): + return S_ISREG(self._stat().mode) + + def exists(self): + return self._stat() + + def link(self): + st = self.path.lstat() + return S_ISLNK(st.mode) + + +class NeverRaised(Exception): + pass + + +class Visitor: + def __init__(self, fil, rec, ignore, bf, sort): + if isinstance(fil, str): + fil = FNMatcher(fil) + if isinstance(rec, str): + self.rec: Callable[[LocalPath], bool] = FNMatcher(rec) + elif not hasattr(rec, "__call__") and rec: + self.rec = lambda path: True + else: + self.rec = rec + self.fil = fil + self.ignore = ignore + self.breadthfirst = bf + self.optsort = cast(Callable[[Any], Any], sorted) if sort else (lambda x: x) + + def gen(self, path): + try: + entries = path.listdir() + except self.ignore: + return + rec = self.rec + dirs = self.optsort( + [p for p in entries if p.check(dir=1) and (rec is None or rec(p))] + ) + if not self.breadthfirst: + for subdir in dirs: + yield from self.gen(subdir) + for p in self.optsort(entries): + if self.fil is None or self.fil(p): + yield p + if self.breadthfirst: + for subdir in dirs: + yield from self.gen(subdir) + + +class FNMatcher: + def __init__(self, pattern): + self.pattern = pattern + + def __call__(self, path): + pattern = self.pattern + + if ( + pattern.find(path.sep) == -1 + and iswin32 + and pattern.find(posixpath.sep) != -1 + ): + # Running on Windows, the pattern has no Windows path separators, + # and the pattern has one or more Posix path separators. Replace + # the Posix path separators with the Windows path separator. + pattern = pattern.replace(posixpath.sep, path.sep) + + if pattern.find(path.sep) == -1: + name = path.basename + else: + name = str(path) # path.strpath # XXX svn? + if not os.path.isabs(pattern): + pattern = "*" + path.sep + pattern + return fnmatch.fnmatch(name, pattern) + + +def map_as_list(func, iter): + return list(map(func, iter)) + + +class Stat: + if TYPE_CHECKING: + + @property + def size(self) -> int: ... + + @property + def mtime(self) -> float: ... + + def __getattr__(self, name: str) -> Any: + return getattr(self._osstatresult, "st_" + name) + + def __init__(self, path, osstatresult): + self.path = path + self._osstatresult = osstatresult + + @property + def owner(self): + if iswin32: + raise NotImplementedError("XXX win32") + import pwd + + entry = error.checked_call(pwd.getpwuid, self.uid) # type:ignore[attr-defined,unused-ignore] + return entry[0] + + @property + def group(self): + """Return group name of file.""" + if iswin32: + raise NotImplementedError("XXX win32") + import grp + + entry = error.checked_call(grp.getgrgid, self.gid) # type:ignore[attr-defined,unused-ignore] + return entry[0] + + def isdir(self): + return S_ISDIR(self._osstatresult.st_mode) + + def isfile(self): + return S_ISREG(self._osstatresult.st_mode) + + def islink(self): + self.path.lstat() + return S_ISLNK(self._osstatresult.st_mode) + + +def getuserid(user): + import pwd + + if not isinstance(user, int): + user = pwd.getpwnam(user)[2] # type:ignore[attr-defined,unused-ignore] + return user + + +def getgroupid(group): + import grp + + if not isinstance(group, int): + group = grp.getgrnam(group)[2] # type:ignore[attr-defined,unused-ignore] + return group + + +class LocalPath: + """Object oriented interface to os.path and other local filesystem + related information. + """ + + class ImportMismatchError(ImportError): + """raised on pyimport() if there is a mismatch of __file__'s""" + + sep = os.sep + + def __init__(self, path=None, expanduser=False): + """Initialize and return a local Path instance. + + Path can be relative to the current directory. + If path is None it defaults to the current working directory. + If expanduser is True, tilde-expansion is performed. + Note that Path instances always carry an absolute path. + Note also that passing in a local path object will simply return + the exact same path object. Use new() to get a new copy. + """ + if path is None: + self.strpath = error.checked_call(os.getcwd) + else: + try: + path = os.fspath(path) + except TypeError: + raise ValueError( + "can only pass None, Path instances " + "or non-empty strings to LocalPath" + ) + if expanduser: + path = os.path.expanduser(path) + self.strpath = abspath(path) + + if sys.platform != "win32": + + def chown(self, user, group, rec=0): + """Change ownership to the given user and group. + user and group may be specified by a number or + by a name. if rec is True change ownership + recursively. + """ + uid = getuserid(user) + gid = getgroupid(group) + if rec: + for x in self.visit(rec=lambda x: x.check(link=0)): + if x.check(link=0): + error.checked_call(os.chown, str(x), uid, gid) + error.checked_call(os.chown, str(self), uid, gid) + + def readlink(self) -> str: + """Return value of a symbolic link.""" + # https://github.com/python/mypy/issues/12278 + return error.checked_call(os.readlink, self.strpath) # type: ignore[arg-type,return-value,unused-ignore] + + def mklinkto(self, oldname): + """Posix style hard link to another name.""" + error.checked_call(os.link, str(oldname), str(self)) + + def mksymlinkto(self, value, absolute=1): + """Create a symbolic link with the given value (pointing to another name).""" + if absolute: + error.checked_call(os.symlink, str(value), self.strpath) + else: + base = self.common(value) + # with posix local paths '/' is always a common base + relsource = self.__class__(value).relto(base) + reldest = self.relto(base) + n = reldest.count(self.sep) + target = self.sep.join(("..",) * n + (relsource,)) + error.checked_call(os.symlink, target, self.strpath) + + def __div__(self, other): + return self.join(os.fspath(other)) + + __truediv__ = __div__ # py3k + + @property + def basename(self): + """Basename part of path.""" + return self._getbyspec("basename")[0] + + @property + def dirname(self): + """Dirname part of path.""" + return self._getbyspec("dirname")[0] + + @property + def purebasename(self): + """Pure base name of the path.""" + return self._getbyspec("purebasename")[0] + + @property + def ext(self): + """Extension of the path (including the '.').""" + return self._getbyspec("ext")[0] + + def read_binary(self): + """Read and return a bytestring from reading the path.""" + with self.open("rb") as f: + return f.read() + + def read_text(self, encoding): + """Read and return a Unicode string from reading the path.""" + with self.open("r", encoding=encoding) as f: + return f.read() + + def read(self, mode="r"): + """Read and return a bytestring from reading the path.""" + with self.open(mode) as f: + return f.read() + + def readlines(self, cr=1): + """Read and return a list of lines from the path. if cr is False, the + newline will be removed from the end of each line.""" + mode = "r" + + if not cr: + content = self.read(mode) + return content.split("\n") + else: + f = self.open(mode) + try: + return f.readlines() + finally: + f.close() + + def load(self): + """(deprecated) return object unpickled from self.read()""" + f = self.open("rb") + try: + import pickle + + return error.checked_call(pickle.load, f) + finally: + f.close() + + def move(self, target): + """Move this path to target.""" + if target.relto(self): + raise error.EINVAL(target, "cannot move path into a subdirectory of itself") + try: + self.rename(target) + except error.EXDEV: # invalid cross-device link + self.copy(target) + self.remove() + + def fnmatch(self, pattern): + """Return true if the basename/fullname matches the glob-'pattern'. + + valid pattern characters:: + + * matches everything + ? matches any single character + [seq] matches any character in seq + [!seq] matches any char not in seq + + If the pattern contains a path-separator then the full path + is used for pattern matching and a '*' is prepended to the + pattern. + + if the pattern doesn't contain a path-separator the pattern + is only matched against the basename. + """ + return FNMatcher(pattern)(self) + + def relto(self, relpath): + """Return a string which is the relative part of the path + to the given 'relpath'. + """ + if not isinstance(relpath, str | LocalPath): + raise TypeError(f"{relpath!r}: not a string or path object") + strrelpath = str(relpath) + if strrelpath and strrelpath[-1] != self.sep: + strrelpath += self.sep + # assert strrelpath[-1] == self.sep + # assert strrelpath[-2] != self.sep + strself = self.strpath + if sys.platform == "win32" or getattr(os, "_name", None) == "nt": + if os.path.normcase(strself).startswith(os.path.normcase(strrelpath)): + return strself[len(strrelpath) :] + elif strself.startswith(strrelpath): + return strself[len(strrelpath) :] + return "" + + def ensure_dir(self, *args): + """Ensure the path joined with args is a directory.""" + return self.ensure(*args, dir=True) + + def bestrelpath(self, dest): + """Return a string which is a relative path from self + (assumed to be a directory) to dest such that + self.join(bestrelpath) == dest and if not such + path can be determined return dest. + """ + try: + if self == dest: + return os.curdir + base = self.common(dest) + if not base: # can be the case on windows + return str(dest) + self2base = self.relto(base) + reldest = dest.relto(base) + if self2base: + n = self2base.count(self.sep) + 1 + else: + n = 0 + lst = [os.pardir] * n + if reldest: + lst.append(reldest) + target = dest.sep.join(lst) + return target + except AttributeError: + return str(dest) + + def exists(self): + return self.check() + + def isdir(self): + return self.check(dir=1) + + def isfile(self): + return self.check(file=1) + + def parts(self, reverse=False): + """Return a root-first list of all ancestor directories + plus the path itself. + """ + current = self + lst = [self] + while 1: + last = current + current = current.dirpath() + if last == current: + break + lst.append(current) + if not reverse: + lst.reverse() + return lst + + def common(self, other): + """Return the common part shared with the other path + or None if there is no common part. + """ + last = None + for x, y in zip(self.parts(), other.parts()): + if x != y: + return last + last = x + return last + + def __add__(self, other): + """Return new path object with 'other' added to the basename""" + return self.new(basename=self.basename + str(other)) + + def visit(self, fil=None, rec=None, ignore=NeverRaised, bf=False, sort=False): + """Yields all paths below the current one + + fil is a filter (glob pattern or callable), if not matching the + path will not be yielded, defaulting to None (everything is + returned) + + rec is a filter (glob pattern or callable) that controls whether + a node is descended, defaulting to None + + ignore is an Exception class that is ignoredwhen calling dirlist() + on any of the paths (by default, all exceptions are reported) + + bf if True will cause a breadthfirst search instead of the + default depthfirst. Default: False + + sort if True will sort entries within each directory level. + """ + yield from Visitor(fil, rec, ignore, bf, sort).gen(self) + + def _sortlist(self, res, sort): + if sort: + if hasattr(sort, "__call__"): + warnings.warn( + DeprecationWarning( + "listdir(sort=callable) is deprecated and breaks on python3" + ), + stacklevel=3, + ) + res.sort(sort) + else: + res.sort() + + def __fspath__(self): + return self.strpath + + def __hash__(self): + s = self.strpath + if iswin32: + s = s.lower() + return hash(s) + + def __eq__(self, other): + s1 = os.fspath(self) + try: + s2 = os.fspath(other) + except TypeError: + return False + if iswin32: + s1 = s1.lower() + try: + s2 = s2.lower() + except AttributeError: + return False + return s1 == s2 + + def __ne__(self, other): + return not (self == other) + + def __lt__(self, other): + return os.fspath(self) < os.fspath(other) + + def __gt__(self, other): + return os.fspath(self) > os.fspath(other) + + def samefile(self, other): + """Return True if 'other' references the same file as 'self'.""" + other = os.fspath(other) + if not isabs(other): + other = abspath(other) + if self == other: + return True + if not hasattr(os.path, "samefile"): + return False + return error.checked_call(os.path.samefile, self.strpath, other) + + def remove(self, rec=1, ignore_errors=False): + """Remove a file or directory (or a directory tree if rec=1). + if ignore_errors is True, errors while removing directories will + be ignored. + """ + if self.check(dir=1, link=0): + if rec: + # force remove of readonly files on windows + if iswin32: + self.chmod(0o700, rec=1) + import shutil + + error.checked_call( + shutil.rmtree, self.strpath, ignore_errors=ignore_errors + ) + else: + error.checked_call(os.rmdir, self.strpath) + else: + if iswin32: + self.chmod(0o700) + error.checked_call(os.remove, self.strpath) + + def computehash(self, hashtype="md5", chunksize=524288): + """Return hexdigest of hashvalue for this file.""" + try: + try: + import hashlib as mod + except ImportError: + if hashtype == "sha1": + hashtype = "sha" + mod = __import__(hashtype) + hash = getattr(mod, hashtype)() + except (AttributeError, ImportError): + raise ValueError(f"Don't know how to compute {hashtype!r} hash") + f = self.open("rb") + try: + while 1: + buf = f.read(chunksize) + if not buf: + return hash.hexdigest() + hash.update(buf) + finally: + f.close() + + def new(self, **kw): + """Create a modified version of this path. + the following keyword arguments modify various path parts:: + + a:/some/path/to/a/file.ext + xx drive + xxxxxxxxxxxxxxxxx dirname + xxxxxxxx basename + xxxx purebasename + xxx ext + """ + obj = object.__new__(self.__class__) + if not kw: + obj.strpath = self.strpath + return obj + drive, dirname, _basename, purebasename, ext = self._getbyspec( + "drive,dirname,basename,purebasename,ext" + ) + if "basename" in kw: + if "purebasename" in kw or "ext" in kw: + raise ValueError(f"invalid specification {kw!r}") + else: + pb = kw.setdefault("purebasename", purebasename) + try: + ext = kw["ext"] + except KeyError: + pass + else: + if ext and not ext.startswith("."): + ext = "." + ext + kw["basename"] = pb + ext + + if "dirname" in kw and not kw["dirname"]: + kw["dirname"] = drive + else: + kw.setdefault("dirname", dirname) + kw.setdefault("sep", self.sep) + obj.strpath = normpath("{dirname}{sep}{basename}".format(**kw)) + return obj + + def _getbyspec(self, spec: str) -> list[str]: + """See new for what 'spec' can be.""" + res = [] + parts = self.strpath.split(self.sep) + + args = filter(None, spec.split(",")) + for name in args: + if name == "drive": + res.append(parts[0]) + elif name == "dirname": + res.append(self.sep.join(parts[:-1])) + else: + basename = parts[-1] + if name == "basename": + res.append(basename) + else: + i = basename.rfind(".") + if i == -1: + purebasename, ext = basename, "" + else: + purebasename, ext = basename[:i], basename[i:] + if name == "purebasename": + res.append(purebasename) + elif name == "ext": + res.append(ext) + else: + raise ValueError(f"invalid part specification {name!r}") + return res + + def dirpath(self, *args, **kwargs): + """Return the directory path joined with any given path arguments.""" + if not kwargs: + path = object.__new__(self.__class__) + path.strpath = dirname(self.strpath) + if args: + path = path.join(*args) + return path + return self.new(basename="").join(*args, **kwargs) + + def join(self, *args: os.PathLike[str], abs: bool = False) -> LocalPath: + """Return a new path by appending all 'args' as path + components. if abs=1 is used restart from root if any + of the args is an absolute path. + """ + sep = self.sep + strargs = [os.fspath(arg) for arg in args] + strpath = self.strpath + if abs: + newargs: list[str] = [] + for arg in reversed(strargs): + if isabs(arg): + strpath = arg + strargs = newargs + break + newargs.insert(0, arg) + # special case for when we have e.g. strpath == "/" + actual_sep = "" if strpath.endswith(sep) else sep + for arg in strargs: + arg = arg.strip(sep) + if iswin32: + # allow unix style paths even on windows. + arg = arg.strip("/") + arg = arg.replace("/", sep) + strpath = strpath + actual_sep + arg + actual_sep = sep + obj = object.__new__(self.__class__) + obj.strpath = normpath(strpath) + return obj + + def open(self, mode="r", ensure=False, encoding=None): + """Return an opened file with the given mode. + + If ensure is True, create parent directories if needed. + """ + if ensure: + self.dirpath().ensure(dir=1) + if encoding: + return error.checked_call( + io.open, + self.strpath, + mode, + encoding=encoding, + ) + return error.checked_call(open, self.strpath, mode) + + def _fastjoin(self, name): + child = object.__new__(self.__class__) + child.strpath = self.strpath + self.sep + name + return child + + def islink(self): + return islink(self.strpath) + + def check(self, **kw): + """Check a path for existence and properties. + + Without arguments, return True if the path exists, otherwise False. + + valid checkers:: + + file = 1 # is a file + file = 0 # is not a file (may not even exist) + dir = 1 # is a dir + link = 1 # is a link + exists = 1 # exists + + You can specify multiple checker definitions, for example:: + + path.check(file=1, link=1) # a link pointing to a file + """ + if not kw: + return exists(self.strpath) + if len(kw) == 1: + if "dir" in kw: + return not kw["dir"] ^ isdir(self.strpath) + if "file" in kw: + return not kw["file"] ^ isfile(self.strpath) + if not kw: + kw = {"exists": 1} + return Checkers(self)._evaluate(kw) + + _patternchars = set("*?[" + os.sep) + + def listdir(self, fil=None, sort=None): + """List directory contents, possibly filter by the given fil func + and possibly sorted. + """ + if fil is None and sort is None: + names = error.checked_call(os.listdir, self.strpath) + return map_as_list(self._fastjoin, names) + if isinstance(fil, str): + if not self._patternchars.intersection(fil): + child = self._fastjoin(fil) + if exists(child.strpath): + return [child] + return [] + fil = FNMatcher(fil) + names = error.checked_call(os.listdir, self.strpath) + res = [] + for name in names: + child = self._fastjoin(name) + if fil is None or fil(child): + res.append(child) + self._sortlist(res, sort) + return res + + def size(self) -> int: + """Return size of the underlying file object""" + return self.stat().size + + def mtime(self) -> float: + """Return last modification time of the path.""" + return self.stat().mtime + + def copy(self, target, mode=False, stat=False): + """Copy path to target. + + If mode is True, will copy permission from path to target. + If stat is True, copy permission, last modification + time, last access time, and flags from path to target. + """ + if self.check(file=1): + if target.check(dir=1): + target = target.join(self.basename) + assert self != target + copychunked(self, target) + if mode: + copymode(self.strpath, target.strpath) + if stat: + copystat(self, target) + else: + + def rec(p): + return p.check(link=0) + + for x in self.visit(rec=rec): + relpath = x.relto(self) + newx = target.join(relpath) + newx.dirpath().ensure(dir=1) + if x.check(link=1): + newx.mksymlinkto(x.readlink()) + continue + elif x.check(file=1): + copychunked(x, newx) + elif x.check(dir=1): + newx.ensure(dir=1) + if mode: + copymode(x.strpath, newx.strpath) + if stat: + copystat(x, newx) + + def rename(self, target): + """Rename this path to target.""" + target = os.fspath(target) + return error.checked_call(os.rename, self.strpath, target) + + def dump(self, obj, bin=1): + """Pickle object into path location""" + f = self.open("wb") + import pickle + + try: + error.checked_call(pickle.dump, obj, f, bin) + finally: + f.close() + + def mkdir(self, *args): + """Create & return the directory joined with args.""" + p = self.join(*args) + error.checked_call(os.mkdir, os.fspath(p)) + return p + + def write_binary(self, data, ensure=False): + """Write binary data into path. If ensure is True create + missing parent directories. + """ + if ensure: + self.dirpath().ensure(dir=1) + with self.open("wb") as f: + f.write(data) + + def write_text(self, data, encoding, ensure=False): + """Write text data into path using the specified encoding. + If ensure is True create missing parent directories. + """ + if ensure: + self.dirpath().ensure(dir=1) + with self.open("w", encoding=encoding) as f: + f.write(data) + + def write(self, data, mode="w", ensure=False): + """Write data into path. If ensure is True create + missing parent directories. + """ + if ensure: + self.dirpath().ensure(dir=1) + if "b" in mode: + if not isinstance(data, bytes): + raise ValueError("can only process bytes") + else: + if not isinstance(data, str): + if not isinstance(data, bytes): + data = str(data) + else: + data = data.decode(sys.getdefaultencoding()) + f = self.open(mode) + try: + f.write(data) + finally: + f.close() + + def _ensuredirs(self): + parent = self.dirpath() + if parent == self: + return self + if parent.check(dir=0): + parent._ensuredirs() + if self.check(dir=0): + try: + self.mkdir() + except error.EEXIST: + # race condition: file/dir created by another thread/process. + # complain if it is not a dir + if self.check(dir=0): + raise + return self + + def ensure(self, *args, **kwargs): + """Ensure that an args-joined path exists (by default as + a file). if you specify a keyword argument 'dir=True' + then the path is forced to be a directory path. + """ + p = self.join(*args) + if kwargs.get("dir", 0): + return p._ensuredirs() + else: + p.dirpath()._ensuredirs() + if not p.check(file=1): + p.open("wb").close() + return p + + @overload + def stat(self, raising: Literal[True] = ...) -> Stat: ... + + @overload + def stat(self, raising: Literal[False]) -> Stat | None: ... + + def stat(self, raising: bool = True) -> Stat | None: + """Return an os.stat() tuple.""" + if raising: + return Stat(self, error.checked_call(os.stat, self.strpath)) + try: + return Stat(self, os.stat(self.strpath)) + except KeyboardInterrupt: + raise + except Exception: + return None + + def lstat(self) -> Stat: + """Return an os.lstat() tuple.""" + return Stat(self, error.checked_call(os.lstat, self.strpath)) + + def setmtime(self, mtime=None): + """Set modification time for the given path. if 'mtime' is None + (the default) then the file's mtime is set to current time. + + Note that the resolution for 'mtime' is platform dependent. + """ + if mtime is None: + return error.checked_call(os.utime, self.strpath, mtime) + try: + return error.checked_call(os.utime, self.strpath, (-1, mtime)) + except error.EINVAL: + return error.checked_call(os.utime, self.strpath, (self.atime(), mtime)) + + def chdir(self): + """Change directory to self and return old current directory""" + try: + old = self.__class__() + except error.ENOENT: + old = None + error.checked_call(os.chdir, self.strpath) + return old + + @contextmanager + def as_cwd(self): + """ + Return a context manager, which changes to the path's dir during the + managed "with" context. + On __enter__ it returns the old dir, which might be ``None``. + """ + old = self.chdir() + try: + yield old + finally: + if old is not None: + old.chdir() + + def realpath(self): + """Return a new path which contains no symbolic links.""" + return self.__class__(os.path.realpath(self.strpath)) + + def atime(self): + """Return last access time of the path.""" + return self.stat().atime + + def __repr__(self): + return f"local({self.strpath!r})" + + def __str__(self): + """Return string representation of the Path.""" + return self.strpath + + def chmod(self, mode, rec=0): + """Change permissions to the given mode. If mode is an + integer it directly encodes the os-specific modes. + if rec is True perform recursively. + """ + if not isinstance(mode, int): + raise TypeError(f"mode {mode!r} must be an integer") + if rec: + for x in self.visit(rec=rec): + error.checked_call(os.chmod, str(x), mode) + error.checked_call(os.chmod, self.strpath, mode) + + def pypkgpath(self): + """Return the Python package path by looking for the last + directory upwards which still contains an __init__.py. + Return None if a pkgpath cannot be determined. + """ + pkgpath = None + for parent in self.parts(reverse=True): + if parent.isdir(): + if not parent.join("__init__.py").exists(): + break + if not isimportable(parent.basename): + break + pkgpath = parent + return pkgpath + + def _ensuresyspath(self, ensuremode, path): + if ensuremode: + s = str(path) + if ensuremode == "append": + if s not in sys.path: + sys.path.append(s) + else: + if s != sys.path[0]: + sys.path.insert(0, s) + + def pyimport(self, modname=None, ensuresyspath=True): + """Return path as an imported python module. + + If modname is None, look for the containing package + and construct an according module name. + The module will be put/looked up in sys.modules. + if ensuresyspath is True then the root dir for importing + the file (taking __init__.py files into account) will + be prepended to sys.path if it isn't there already. + If ensuresyspath=="append" the root dir will be appended + if it isn't already contained in sys.path. + if ensuresyspath is False no modification of syspath happens. + + Special value of ensuresyspath=="importlib" is intended + purely for using in pytest, it is capable only of importing + separate .py files outside packages, e.g. for test suite + without any __init__.py file. It effectively allows having + same-named test modules in different places and offers + mild opt-in via this option. Note that it works only in + recent versions of python. + """ + if not self.check(): + raise error.ENOENT(self) + + if ensuresyspath == "importlib": + if modname is None: + modname = self.purebasename + spec = importlib.util.spec_from_file_location(modname, str(self)) + if spec is None or spec.loader is None: + raise ImportError(f"Can't find module {modname} at location {self!s}") + mod = importlib.util.module_from_spec(spec) + spec.loader.exec_module(mod) + return mod + + pkgpath = None + if modname is None: + pkgpath = self.pypkgpath() + if pkgpath is not None: + pkgroot = pkgpath.dirpath() + names = self.new(ext="").relto(pkgroot).split(self.sep) + if names[-1] == "__init__": + names.pop() + modname = ".".join(names) + else: + pkgroot = self.dirpath() + modname = self.purebasename + + self._ensuresyspath(ensuresyspath, pkgroot) + __import__(modname) + mod = sys.modules[modname] + if self.basename == "__init__.py": + return mod # we don't check anything as we might + # be in a namespace package ... too icky to check + modfile = mod.__file__ + assert modfile is not None + if modfile[-4:] in (".pyc", ".pyo"): + modfile = modfile[:-1] + elif modfile.endswith("$py.class"): + modfile = modfile[:-9] + ".py" + if modfile.endswith(os.sep + "__init__.py"): + if self.basename != "__init__.py": + modfile = modfile[:-12] + try: + issame = self.samefile(modfile) + except error.ENOENT: + issame = False + if not issame: + ignore = os.getenv("PY_IGNORE_IMPORTMISMATCH") + if ignore != "1": + raise self.ImportMismatchError(modname, modfile, self) + return mod + else: + try: + return sys.modules[modname] + except KeyError: + # we have a custom modname, do a pseudo-import + import types + + mod = types.ModuleType(modname) + mod.__file__ = str(self) + sys.modules[modname] = mod + try: + with open(str(self), "rb") as f: + exec(f.read(), mod.__dict__) + except BaseException: + del sys.modules[modname] + raise + return mod + + def sysexec(self, *argv: os.PathLike[str], **popen_opts: Any) -> str: + """Return stdout text from executing a system child process, + where the 'self' path points to executable. + The process is directly invoked and not through a system shell. + """ + from subprocess import PIPE + from subprocess import Popen + + popen_opts.pop("stdout", None) + popen_opts.pop("stderr", None) + proc = Popen( + [str(self)] + [str(arg) for arg in argv], + **popen_opts, + stdout=PIPE, + stderr=PIPE, + ) + stdout: str | bytes + stdout, stderr = proc.communicate() + ret = proc.wait() + if isinstance(stdout, bytes): + stdout = stdout.decode(sys.getdefaultencoding()) + if ret != 0: + if isinstance(stderr, bytes): + stderr = stderr.decode(sys.getdefaultencoding()) + raise RuntimeError( + ret, + ret, + str(self), + stdout, + stderr, + ) + return stdout + + @classmethod + def sysfind(cls, name, checker=None, paths=None): + """Return a path object found by looking at the systems + underlying PATH specification. If the checker is not None + it will be invoked to filter matching paths. If a binary + cannot be found, None is returned + Note: This is probably not working on plain win32 systems + but may work on cygwin. + """ + if isabs(name): + p = local(name) + if p.check(file=1): + return p + else: + if paths is None: + if iswin32: + paths = os.environ["Path"].split(";") + if "" not in paths and "." not in paths: + paths.append(".") + try: + systemroot = os.environ["SYSTEMROOT"] + except KeyError: + pass + else: + paths = [ + path.replace("%SystemRoot%", systemroot) for path in paths + ] + else: + paths = os.environ["PATH"].split(":") + tryadd = [] + if iswin32: + tryadd += os.environ["PATHEXT"].split(os.pathsep) + tryadd.append("") + + for x in paths: + for addext in tryadd: + p = local(x).join(name, abs=True) + addext + try: + if p.check(file=1): + if checker: + if not checker(p): + continue + return p + except error.EACCES: + pass + return None + + @classmethod + def _gethomedir(cls): + try: + x = os.environ["HOME"] + except KeyError: + try: + x = os.environ["HOMEDRIVE"] + os.environ["HOMEPATH"] + except KeyError: + return None + return cls(x) + + # """ + # special class constructors for local filesystem paths + # """ + @classmethod + def get_temproot(cls): + """Return the system's temporary directory + (where tempfiles are usually created in) + """ + import tempfile + + return local(tempfile.gettempdir()) + + @classmethod + def mkdtemp(cls, rootdir=None): + """Return a Path object pointing to a fresh new temporary directory + (which we created ourselves). + """ + import tempfile + + if rootdir is None: + rootdir = cls.get_temproot() + path = error.checked_call(tempfile.mkdtemp, dir=str(rootdir)) + return cls(path) + + @classmethod + def make_numbered_dir( + cls, prefix="session-", rootdir=None, keep=3, lock_timeout=172800 + ): # two days + """Return unique directory with a number greater than the current + maximum one. The number is assumed to start directly after prefix. + if keep is true directories with a number less than (maxnum-keep) + will be removed. If .lock files are used (lock_timeout non-zero), + algorithm is multi-process safe. + """ + if rootdir is None: + rootdir = cls.get_temproot() + + nprefix = prefix.lower() + + def parse_num(path): + """Parse the number out of a path (if it matches the prefix)""" + nbasename = path.basename.lower() + if nbasename.startswith(nprefix): + try: + return int(nbasename[len(nprefix) :]) + except ValueError: + pass + + def create_lockfile(path): + """Exclusively create lockfile. Throws when failed""" + mypid = os.getpid() + lockfile = path.join(".lock") + if hasattr(lockfile, "mksymlinkto"): + lockfile.mksymlinkto(str(mypid)) + else: + fd = error.checked_call( + os.open, str(lockfile), os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o644 + ) + with os.fdopen(fd, "w") as f: + f.write(str(mypid)) + return lockfile + + def atexit_remove_lockfile(lockfile): + """Ensure lockfile is removed at process exit""" + mypid = os.getpid() + + def try_remove_lockfile(): + # in a fork() situation, only the last process should + # remove the .lock, otherwise the other processes run the + # risk of seeing their temporary dir disappear. For now + # we remove the .lock in the parent only (i.e. we assume + # that the children finish before the parent). + if os.getpid() != mypid: + return + try: + lockfile.remove() + except error.Error: + pass + + atexit.register(try_remove_lockfile) + + # compute the maximum number currently in use with the prefix + lastmax = None + while True: + maxnum = -1 + for path in rootdir.listdir(): + num = parse_num(path) + if num is not None: + maxnum = max(maxnum, num) + + # make the new directory + try: + udir = rootdir.mkdir(prefix + str(maxnum + 1)) + if lock_timeout: + lockfile = create_lockfile(udir) + atexit_remove_lockfile(lockfile) + except (error.EEXIST, error.ENOENT, error.EBUSY): + # race condition (1): another thread/process created the dir + # in the meantime - try again + # race condition (2): another thread/process spuriously acquired + # lock treating empty directory as candidate + # for removal - try again + # race condition (3): another thread/process tried to create the lock at + # the same time (happened in Python 3.3 on Windows) + # https://ci.appveyor.com/project/pytestbot/py/build/1.0.21/job/ffi85j4c0lqwsfwa + if lastmax == maxnum: + raise + lastmax = maxnum + continue + break + + def get_mtime(path): + """Read file modification time""" + try: + return path.lstat().mtime + except error.Error: + pass + + garbage_prefix = prefix + "garbage-" + + def is_garbage(path): + """Check if path denotes directory scheduled for removal""" + bn = path.basename + return bn.startswith(garbage_prefix) + + # prune old directories + udir_time = get_mtime(udir) + if keep and udir_time: + for path in rootdir.listdir(): + num = parse_num(path) + if num is not None and num <= (maxnum - keep): + try: + # try acquiring lock to remove directory as exclusive user + if lock_timeout: + create_lockfile(path) + except (error.EEXIST, error.ENOENT, error.EBUSY): + path_time = get_mtime(path) + if not path_time: + # assume directory doesn't exist now + continue + if abs(udir_time - path_time) < lock_timeout: + # assume directory with lockfile exists + # and lock timeout hasn't expired yet + continue + + # path dir locked for exclusive use + # and scheduled for removal to avoid another thread/process + # treating it as a new directory or removal candidate + garbage_path = rootdir.join(garbage_prefix + str(uuid.uuid4())) + try: + path.rename(garbage_path) + garbage_path.remove(rec=1) + except KeyboardInterrupt: + raise + except Exception: # this might be error.Error, WindowsError ... + pass + if is_garbage(path): + try: + path.remove(rec=1) + except KeyboardInterrupt: + raise + except Exception: # this might be error.Error, WindowsError ... + pass + + # make link... + try: + username = os.environ["USER"] # linux, et al + except KeyError: + try: + username = os.environ["USERNAME"] # windows + except KeyError: + username = "current" + + src = str(udir) + dest = src[: src.rfind("-")] + "-" + username + try: + os.unlink(dest) + except OSError: + pass + try: + os.symlink(src, dest) + except (OSError, AttributeError, NotImplementedError): + pass + + return udir + + +def copymode(src, dest): + """Copy permission from src to dst.""" + import shutil + + shutil.copymode(src, dest) + + +def copystat(src, dest): + """Copy permission, last modification time, + last access time, and flags from src to dst.""" + import shutil + + shutil.copystat(str(src), str(dest)) + + +def copychunked(src, dest): + chunksize = 524288 # half a meg of bytes + fsrc = src.open("rb") + try: + fdest = dest.open("wb") + try: + while 1: + buf = fsrc.read(chunksize) + if not buf: + break + fdest.write(buf) + finally: + fdest.close() + finally: + fsrc.close() + + +def isimportable(name): + if name and (name[0].isalpha() or name[0] == "_"): + name = name.replace("_", "") + return not name or name.isalnum() + + +local = LocalPath diff --git a/py311/lib/python3.11/site-packages/_pytest/assertion/__init__.py b/py311/lib/python3.11/site-packages/_pytest/assertion/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..22f3ca8e258cc48effeb34154821f8b1e5cf151b --- /dev/null +++ b/py311/lib/python3.11/site-packages/_pytest/assertion/__init__.py @@ -0,0 +1,208 @@ +# 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 Protocol +from typing import TYPE_CHECKING + +from _pytest.assertion import rewrite +from _pytest.assertion import truncate +from _pytest.assertion import util +from _pytest.assertion.rewrite import assertstate_key +from _pytest.config import Config +from _pytest.config import hookimpl +from _pytest.config.argparsing import Parser +from _pytest.nodes import Item + + +if TYPE_CHECKING: + from _pytest.main import Session + + +def pytest_addoption(parser: Parser) -> None: + group = parser.getgroup("debugconfig") + group.addoption( + "--assert", + action="store", + dest="assertmode", + choices=("rewrite", "plain"), + default="rewrite", + metavar="MODE", + help=( + "Control assertion debugging tools.\n" + "'plain' performs no assertion debugging.\n" + "'rewrite' (the default) rewrites assert statements in test modules" + " on import to provide assert expression information." + ), + ) + parser.addini( + "enable_assertion_pass_hook", + type="bool", + default=False, + help="Enables the pytest_assertion_pass hook. " + "Make sure to delete any previously generated pyc cache files.", + ) + + 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. + + This function will make sure that this module or all modules inside + the package will get their assert statements rewritten. + Thus you should make sure to call this before the module is + actually imported, usually in your __init__.py if you are a plugin + using a package. + + :param names: The module names to register. + """ + for name in names: + if not isinstance(name, str): + msg = "expected module names as *args, got {0} instead" # type: ignore[unreachable] + raise TypeError(msg.format(repr(names))) + rewrite_hook: RewriteHook + for hook in sys.meta_path: + if isinstance(hook, rewrite.AssertionRewritingHook): + rewrite_hook = hook + break + else: + rewrite_hook = DummyRewriteHook() + rewrite_hook.mark_rewrite(*names) + + +class RewriteHook(Protocol): + def mark_rewrite(self, *names: str) -> None: ... + + +class DummyRewriteHook: + """A no-op import hook for when rewriting is disabled.""" + + def mark_rewrite(self, *names: str) -> None: + pass + + +class AssertionState: + """State for the assertion plugin.""" + + def __init__(self, config: Config, mode) -> None: + self.mode = mode + self.trace = config.trace.root.get("assertion") + self.hook: rewrite.AssertionRewritingHook | None = None + + +def install_importhook(config: Config) -> rewrite.AssertionRewritingHook: + """Try to install the rewrite hook, raise SystemError if it fails.""" + config.stash[assertstate_key] = AssertionState(config, "rewrite") + config.stash[assertstate_key].hook = hook = rewrite.AssertionRewritingHook(config) + sys.meta_path.insert(0, hook) + config.stash[assertstate_key].trace("installed rewrite import hook") + + def undo() -> None: + hook = config.stash[assertstate_key].hook + if hook is not None and hook in sys.meta_path: + sys.meta_path.remove(hook) + + config.add_cleanup(undo) + return hook + + +def pytest_collection(session: Session) -> None: + # This hook is only called when test modules are collected + # so for example not in the managing process of pytest-xdist + # (which does not collect test modules). + assertstate = session.config.stash.get(assertstate_key, None) + if assertstate: + if assertstate.hook is not None: + assertstate.hook.set_session(session) + + +@hookimpl(wrapper=True, tryfirst=True) +def pytest_runtest_protocol(item: Item) -> Generator[None, object, object]: + """Setup the pytest_assertrepr_compare and pytest_assertion_pass hooks. + + The rewrite module will use util._reprcompare if it exists to use custom + reporting via the pytest_assertrepr_compare hook. This sets up this custom + comparison for the test. + """ + ihook = item.ihook + + def callbinrepr(op, left: object, right: object) -> str | None: + """Call the pytest_assertrepr_compare hook and prepare the result. + + This uses the first result from the hook and then ensures the + following: + * Overly verbose explanations are truncated unless configured otherwise + (eg. if running in verbose mode). + * Embedded newlines are escaped to help util.format_explanation() + later. + * If the rewrite mode is used embedded %-characters are replaced + to protect later % formatting. + + The result can be formatted by util.format_explanation() for + pretty printing. + """ + hook_result = ihook.pytest_assertrepr_compare( + config=item.config, op=op, left=left, right=right + ) + for new_expl in hook_result: + if new_expl: + new_expl = truncate.truncate_if_required(new_expl, item) + new_expl = [line.replace("\n", "\\n") for line in new_expl] + res = "\n~".join(new_expl) + if item.config.getvalue("assertmode") == "rewrite": + res = res.replace("%", "%%") + return res + return None + + saved_assert_hooks = util._reprcompare, util._assertion_pass + util._reprcompare = callbinrepr + util._config = item.config + + if ihook.pytest_assertion_pass.get_hookimpls(): + + def call_assertion_pass_hook(lineno: int, orig: str, expl: str) -> None: + ihook.pytest_assertion_pass(item=item, lineno=lineno, orig=orig, expl=expl) + + util._assertion_pass = call_assertion_pass_hook + + try: + return (yield) + finally: + util._reprcompare, util._assertion_pass = saved_assert_hooks + util._config = None + + +def pytest_sessionfinish(session: Session) -> None: + assertstate = session.config.stash.get(assertstate_key, None) + if assertstate: + if assertstate.hook is not None: + assertstate.hook.set_session(None) + + +def pytest_assertrepr_compare( + config: Config, op: str, left: Any, right: Any +) -> list[str] | None: + return util.assertrepr_compare(config=config, op=op, left=left, right=right) diff --git a/py311/lib/python3.11/site-packages/_pytest/assertion/rewrite.py b/py311/lib/python3.11/site-packages/_pytest/assertion/rewrite.py new file mode 100644 index 0000000000000000000000000000000000000000..566549d66f25dfe9f144cb451f370da2635c1072 --- /dev/null +++ b/py311/lib/python3.11/site-packages/_pytest/assertion/rewrite.py @@ -0,0 +1,1202 @@ +"""Rewrite assertion AST to produce nice error messages.""" + +from __future__ import annotations + +import ast +from collections import defaultdict +from collections.abc import Callable +from collections.abc import Iterable +from collections.abc import Iterator +from collections.abc import Sequence +import errno +import functools +import importlib.abc +import importlib.machinery +import importlib.util +import io +import itertools +import marshal +import os +from pathlib import Path +from pathlib import PurePath +import struct +import sys +import tokenize +import types +from typing import IO +from typing import TYPE_CHECKING + + +if sys.version_info >= (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.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 + + +class Sentinel: + pass + + +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_TAIL = "." + PYTEST_TAG + PYC_EXT + +# Special marker that denotes we have just left a scope definition +_SCOPE_END_MARKER = Sentinel() + + +class AssertionRewritingHook(importlib.abc.MetaPathFinder, importlib.abc.Loader): + """PEP302/PEP451 import hook which rewrites asserts.""" + + def __init__(self, config: Config) -> None: + self.config = config + try: + self.fnpats = config.getini("python_files") + except ValueError: + self.fnpats = ["test_*.py", "*_test.py"] + self.session: Session | None = None + self._rewritten_names: dict[str, Path] = {} + self._must_rewrite: set[str] = set() + # flag to guard against trying to rewrite a pyc file while we are already writing another pyc file, + # which might result in infinite recursion (#3506) + self._writing_pyc = False + self._basenames_to_check_rewrite = {"conftest"} + self._marked_for_rewrite_cache: dict[str, bool] = {} + self._session_paths_checked = False + + def set_session(self, session: Session | None) -> None: + self.session = session + self._session_paths_checked = False + + # Indirection so we can mock calls to find_spec originated from the hook during testing + _find_spec = importlib.machinery.PathFinder.find_spec + + def find_spec( + self, + name: str, + path: Sequence[str | bytes] | None = None, + target: types.ModuleType | None = None, + ) -> importlib.machinery.ModuleSpec | None: + if self._writing_pyc: + return None + state = self.config.stash[assertstate_key] + if self._early_rewrite_bailout(name, state): + return None + state.trace(f"find_module called for: {name}") + + # Type ignored because mypy is confused about the `self` binding here. + spec = self._find_spec(name, path) # type: ignore + + if spec is None and path is not None: + # With --import-mode=importlib, PathFinder cannot find spec without modifying `sys.path`, + # causing inability to assert rewriting (#12659). + # At this point, try using the file path to find the module spec. + for _path_str in path: + spec = importlib.util.spec_from_file_location(name, _path_str) + if spec is not None: + break + + if ( + # the import machinery could not find a file to import + spec is None + # this is a namespace package (without `__init__.py`) + # there's nothing to rewrite there + or spec.origin is None + # we can only rewrite source files + or not isinstance(spec.loader, importlib.machinery.SourceFileLoader) + # if the file doesn't exist, we can't rewrite it + or not os.path.exists(spec.origin) + ): + return None + else: + fn = spec.origin + + if not self._should_rewrite(name, fn, state): + return None + + return importlib.util.spec_from_file_location( + name, + fn, + loader=self, + submodule_search_locations=spec.submodule_search_locations, + ) + + def create_module( + self, spec: importlib.machinery.ModuleSpec + ) -> types.ModuleType | None: + return None # default behaviour is fine + + def exec_module(self, module: types.ModuleType) -> None: + assert module.__spec__ is not None + assert module.__spec__.origin is not None + fn = Path(module.__spec__.origin) + state = self.config.stash[assertstate_key] + + self._rewritten_names[module.__name__] = fn + + # The requested module looks like a test file, so rewrite it. This is + # the most magical part of the process: load the source, rewrite the + # asserts, and load the rewritten source. We also cache the rewritten + # module code in a special pyc. We must be aware of the possibility of + # concurrent pytest processes rewriting and loading pycs. To avoid + # tricky race conditions, we maintain the following invariant: The + # cached pyc is always a complete, valid pyc. Operations on it must be + # atomic. POSIX's atomic rename comes in handy. + write = not sys.dont_write_bytecode + cache_dir = get_cache_dir(fn) + if write: + ok = try_makedirs(cache_dir) + if not ok: + write = False + state.trace(f"read only directory: {cache_dir}") + + cache_name = fn.name[:-3] + PYC_TAIL + pyc = cache_dir / cache_name + # Notice that even if we're in a read-only directory, I'm going + # to check for a cached pyc. This may not be optimal... + co = _read_pyc(fn, pyc, state.trace) + if co is None: + state.trace(f"rewriting {fn!r}") + source_stat, co = _rewrite_test(fn, self.config) + if write: + self._writing_pyc = True + try: + _write_pyc(state, co, source_stat, pyc) + finally: + self._writing_pyc = False + else: + state.trace(f"found cached rewritten pyc for {fn}") + exec(co, module.__dict__) + + def _early_rewrite_bailout(self, name: str, state: AssertionState) -> bool: + """A fast way to get out of rewriting modules. + + Profiling has shown that the call to PathFinder.find_spec (inside of + the find_spec from this class) is a major slowdown, so, this method + tries to filter what we're sure won't be rewritten before getting to + it. + """ + if self.session is not None and not self._session_paths_checked: + self._session_paths_checked = True + for initial_path in self.session._initialpaths: + # Make something as c:/projects/my_project/path.py -> + # ['c:', 'projects', 'my_project', 'path.py'] + parts = str(initial_path).split(os.sep) + # add 'path' to basenames to be checked. + self._basenames_to_check_rewrite.add(os.path.splitext(parts[-1])[0]) + + # Note: conftest already by default in _basenames_to_check_rewrite. + parts = name.split(".") + if parts[-1] in self._basenames_to_check_rewrite: + return False + + # For matching the name it must be as if it was a filename. + path = PurePath(*parts).with_suffix(".py") + + for pat in self.fnpats: + # if the pattern contains subdirectories ("tests/**.py" for example) we can't bail out based + # on the name alone because we need to match against the full path + if os.path.dirname(pat): + return False + if fnmatch_ex(pat, path): + return False + + if self._is_marked_for_rewrite(name, state): + return False + + state.trace(f"early skip of rewriting module: {name}") + return True + + def _should_rewrite(self, name: str, fn: str, state: AssertionState) -> bool: + # always rewrite conftest files + if os.path.basename(fn) == "conftest.py": + state.trace(f"rewriting conftest file: {fn!r}") + return True + + if self.session is not None: + if self.session.isinitpath(absolutepath(fn)): + state.trace(f"matched test file (was specified on cmdline): {fn!r}") + return True + + # modules not passed explicitly on the command line are only + # rewritten if they match the naming convention for test files + fn_path = PurePath(fn) + for pat in self.fnpats: + if fnmatch_ex(pat, fn_path): + state.trace(f"matched test file {fn!r}") + return True + + return self._is_marked_for_rewrite(name, state) + + def _is_marked_for_rewrite(self, name: str, state: AssertionState) -> bool: + try: + return self._marked_for_rewrite_cache[name] + except KeyError: + for marked in self._must_rewrite: + if name == marked or name.startswith(marked + "."): + state.trace(f"matched marked file {name!r} (from {marked!r})") + self._marked_for_rewrite_cache[name] = True + return True + + self._marked_for_rewrite_cache[name] = False + return False + + def mark_rewrite(self, *names: str) -> None: + """Mark import names as needing to be rewritten. + + The named module or package as well as any nested modules will + be rewritten on import. + """ + already_imported = ( + set(names).intersection(sys.modules).difference(self._rewritten_names) + ) + for name in already_imported: + mod = sys.modules[name] + if not AssertionRewriter.is_rewrite_disabled( + mod.__doc__ or "" + ) and not isinstance(mod.__loader__, type(self)): + self._warn_already_imported(name) + self._must_rewrite.update(names) + self._marked_for_rewrite_cache.clear() + + def _warn_already_imported(self, name: str) -> None: + from _pytest.warning_types import PytestAssertRewriteWarning + + self.config.issue_config_time_warning( + PytestAssertRewriteWarning( + f"Module already imported so cannot be rewritten; {name}" + ), + stacklevel=5, + ) + + def get_data(self, pathname: str | bytes) -> bytes: + """Optional PEP302 get_data API.""" + with open(pathname, "rb") as f: + return f.read() + + 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( + fp: IO[bytes], source_stat: os.stat_result, co: types.CodeType +) -> None: + # Technically, we don't have to have the same pyc format as + # (C)Python, since these "pycs" should never be seen by builtin + # import. However, there's little reason to deviate. + fp.write(importlib.util.MAGIC_NUMBER) + # https://www.python.org/dev/peps/pep-0552/ + flags = b"\x00\x00\x00\x00" + fp.write(flags) + # as of now, bytecode header expects 32-bit numbers for size and mtime (#4903) + mtime = int(source_stat.st_mtime) & 0xFFFFFFFF + size = source_stat.st_size & 0xFFFFFFFF + # " bool: + proc_pyc = f"{pyc}.{os.getpid()}" + try: + with open(proc_pyc, "wb") as fp: + _write_pyc_fp(fp, source_stat, co) + except OSError as e: + state.trace(f"error writing pyc file at {proc_pyc}: errno={e.errno}") + return False + + try: + os.replace(proc_pyc, pyc) + except OSError as e: + state.trace(f"error writing pyc file at {pyc}: {e}") + # we ignore any failure to write the cache file + # there are many reasons, permission-denied, pycache dir being a + # file etc. + return False + return True + + +def _rewrite_test(fn: Path, config: Config) -> tuple[os.stat_result, types.CodeType]: + """Read and rewrite *fn* and return the code object.""" + stat = os.stat(fn) + source = fn.read_bytes() + strfn = str(fn) + tree = ast.parse(source, filename=strfn) + rewrite_asserts(tree, source, strfn, config) + co = compile(tree, strfn, "exec", dont_inherit=True) + return stat, co + + +def _read_pyc( + source: Path, pyc: Path, trace: Callable[[str], None] = lambda x: None +) -> types.CodeType | None: + """Possibly read a pytest pyc containing rewritten code. + + Return rewritten code if successful or None if not. + """ + try: + fp = open(pyc, "rb") + except OSError: + return None + with fp: + try: + stat_result = os.stat(source) + mtime = int(stat_result.st_mtime) + size = stat_result.st_size + data = fp.read(16) + except OSError as e: + trace(f"_read_pyc({source}): OSError {e}") + return None + # Check for invalid or out of date pyc file. + if len(data) != (16): + trace(f"_read_pyc({source}): invalid pyc (too short)") + return None + if data[:4] != importlib.util.MAGIC_NUMBER: + trace(f"_read_pyc({source}): invalid pyc (bad magic number)") + return None + if data[4:8] != b"\x00\x00\x00\x00": + trace(f"_read_pyc({source}): invalid pyc (unsupported flags)") + return None + mtime_data = data[8:12] + if int.from_bytes(mtime_data, "little") != mtime & 0xFFFFFFFF: + trace(f"_read_pyc({source}): out of date") + return None + size_data = data[12:16] + if int.from_bytes(size_data, "little") != size & 0xFFFFFFFF: + trace(f"_read_pyc({source}): invalid pyc (incorrect size)") + return None + try: + co = marshal.load(fp) + except Exception as e: + trace(f"_read_pyc({source}): marshal.load error {e}") + return None + if not isinstance(co, types.CodeType): + trace(f"_read_pyc({source}): not a code object") + return None + return co + + +def rewrite_asserts( + mod: ast.Module, + source: bytes, + module_path: str | None = None, + config: Config | None = None, +) -> None: + """Rewrite the assert statements in mod.""" + AssertionRewriter(module_path, config, source).run(mod) + + +def _saferepr(obj: object) -> str: + r"""Get a safe repr of an object for assertion error messages. + + The assertion formatting (util.format_explanation()) requires + newlines to be escaped since they are a special character for it. + Normally assertion.util.format_explanation() does this but for a + custom repr it is possible to contain one of the special escape + sequences, especially '\n{' and '\n}' are likely to be present in + JSON reprs. + """ + if isinstance(obj, types.MethodType): + # for bound methods, skip redundant information + return obj.__name__ + + maxsize = _get_maxsize_for_saferepr(util._config) + if not maxsize: + return saferepr_unlimited(obj).replace("\n", "\\n") + return saferepr(obj, maxsize=maxsize).replace("\n", "\\n") + + +def _get_maxsize_for_saferepr(config: Config | None) -> int | None: + """Get `maxsize` configuration for saferepr based on the given config object.""" + if config is None: + verbosity = 0 + else: + verbosity = config.get_verbosity(Config.VERBOSITY_ASSERTIONS) + if verbosity >= 2: + return None + if verbosity >= 1: + return DEFAULT_REPR_MAX_SIZE * 10 + return DEFAULT_REPR_MAX_SIZE + + +def _format_assertmsg(obj: object) -> str: + r"""Format the custom assertion message given. + + For strings this simply replaces newlines with '\n~' so that + util.format_explanation() will preserve them instead of escaping + newlines. For other objects saferepr() is used first. + """ + # reprlib appears to have a bug which means that if a string + # contains a newline it gets escaped, however if an object has a + # .__repr__() which contains newlines it does not get escaped. + # However in either case we want to preserve the newline. + replaces = [("\n", "\n~"), ("%", "%%")] + if not isinstance(obj, str): + obj = saferepr(obj, _get_maxsize_for_saferepr(util._config)) + replaces.append(("\\n", "\n~")) + + for r1, r2 in replaces: + obj = obj.replace(r1, r2) + + return obj + + +def _should_repr_global_name(obj: object) -> bool: + if callable(obj): + # For pytest fixtures the __repr__ method provides more information than the function name. + return isinstance(obj, FixtureFunctionDefinition) + + try: + return not hasattr(obj, "__name__") + except Exception: + return True + + +def _format_boolop(explanations: Iterable[str], is_or: bool) -> str: + explanation = "(" + ((is_or and " or ") or " and ").join(explanations) + ")" + return explanation.replace("%", "%%") + + +def _call_reprcompare( + ops: Sequence[str], + results: Sequence[bool], + expls: Sequence[str], + each_obj: Sequence[object], +) -> str: + for i, res, expl in zip(range(len(ops)), results, expls, strict=True): + try: + done = not res + except Exception: + done = True + if done: + break + if util._reprcompare is not None: + custom = util._reprcompare(ops[i], each_obj[i], each_obj[i + 1]) + if custom is not None: + return custom + return expl + + +def _call_assertion_pass(lineno: int, orig: str, expl: str) -> None: + if util._assertion_pass is not None: + util._assertion_pass(lineno, orig, expl) + + +def _check_if_assertion_pass_impl() -> bool: + """Check if any plugins implement the pytest_assertion_pass hook + in order not to generate explanation unnecessarily (might be expensive).""" + return True if util._assertion_pass else False + + +UNARY_MAP = {ast.Not: "not %s", ast.Invert: "~%s", ast.USub: "-%s", ast.UAdd: "+%s"} + +BINOP_MAP = { + ast.BitOr: "|", + ast.BitXor: "^", + ast.BitAnd: "&", + ast.LShift: "<<", + ast.RShift: ">>", + ast.Add: "+", + ast.Sub: "-", + ast.Mult: "*", + ast.Div: "/", + ast.FloorDiv: "//", + ast.Mod: "%%", # escaped for string formatting + ast.Eq: "==", + ast.NotEq: "!=", + ast.Lt: "<", + ast.LtE: "<=", + ast.Gt: ">", + ast.GtE: ">=", + ast.Pow: "**", + ast.Is: "is", + ast.IsNot: "is not", + ast.In: "in", + ast.NotIn: "not in", + ast.MatMult: "@", +} + + +def traverse_node(node: ast.AST) -> Iterator[ast.AST]: + """Recursively yield node and all its children in depth-first order.""" + yield node + for child in ast.iter_child_nodes(node): + yield from traverse_node(child) + + +@functools.lru_cache(maxsize=1) +def _get_assertion_exprs(src: bytes) -> dict[int, str]: + """Return a mapping from {lineno: "assertion test expression"}.""" + ret: dict[int, str] = {} + + depth = 0 + lines: list[str] = [] + assert_lineno: int | None = None + seen_lines: set[int] = set() + + def _write_and_reset() -> None: + nonlocal depth, lines, assert_lineno, seen_lines + assert assert_lineno is not None + ret[assert_lineno] = "".join(lines).rstrip().rstrip("\\") + depth = 0 + lines = [] + assert_lineno = None + seen_lines = set() + + tokens = tokenize.tokenize(io.BytesIO(src).readline) + for tp, source, (lineno, offset), _, line in tokens: + if tp == tokenize.NAME and source == "assert": + assert_lineno = lineno + elif assert_lineno is not None: + # keep track of depth for the assert-message `,` lookup + if tp == tokenize.OP and source in "([{": + depth += 1 + elif tp == tokenize.OP and source in ")]}": + depth -= 1 + + if not lines: + lines.append(line[offset:]) + seen_lines.add(lineno) + # a non-nested comma separates the expression from the message + elif depth == 0 and tp == tokenize.OP and source == ",": + # one line assert with message + if lineno in seen_lines and len(lines) == 1: + offset_in_trimmed = offset + len(lines[-1]) - len(line) + lines[-1] = lines[-1][:offset_in_trimmed] + # multi-line assert with message + elif lineno in seen_lines: + lines[-1] = lines[-1][:offset] + # multi line assert with escaped newline before message + else: + lines.append(line[:offset]) + _write_and_reset() + elif tp in {tokenize.NEWLINE, tokenize.ENDMARKER}: + _write_and_reset() + elif lines and lineno not in seen_lines: + lines.append(line) + seen_lines.add(lineno) + + return ret + + +class AssertionRewriter(ast.NodeVisitor): + """Assertion rewriting implementation. + + The main entrypoint is to call .run() with an ast.Module instance, + this will then find all the assert statements and rewrite them to + provide intermediate values and a detailed assertion error. See + http://pybites.blogspot.be/2011/07/behind-scenes-of-pytests-new-assertion.html + for an overview of how this works. + + The entry point here is .run() which will iterate over all the + statements in an ast.Module and for each ast.Assert statement it + finds call .visit() with it. Then .visit_Assert() takes over and + is responsible for creating new ast statements to replace the + original assert statement: it rewrites the test of an assertion + to provide intermediate values and replace it with an if statement + which raises an assertion error with a detailed explanation in + case the expression is false and calls pytest_assertion_pass hook + if expression is true. + + For this .visit_Assert() uses the visitor pattern to visit all the + AST nodes of the ast.Assert.test field, each visit call returning + an AST node and the corresponding explanation string. During this + state is kept in several instance attributes: + + :statements: All the AST statements which will replace the assert + statement. + + :variables: This is populated by .variable() with each variable + used by the statements so that they can all be set to None at + the end of the statements. + + :variable_counter: Counter to create new unique variables needed + by statements. Variables are created using .variable() and + have the form of "@py_assert0". + + :expl_stmts: The AST statements which will be executed to get + data from the assertion. This is the code which will construct + the detailed assertion message that is used in the AssertionError + or for the pytest_assertion_pass hook. + + :explanation_specifiers: A dict filled by .explanation_param() + with %-formatting placeholders and their corresponding + expressions to use in the building of an assertion message. + This is used by .pop_format_context() to build a message. + + :stack: A stack of the explanation_specifiers dicts maintained by + .push_format_context() and .pop_format_context() which allows + to build another %-formatted string while already building one. + + :scope: A tuple containing the current scope used for variables_overwrite. + + :variables_overwrite: A dict filled with references to variables + that change value within an assert. This happens when a variable is + reassigned with the walrus operator + + This state, except the variables_overwrite, is reset on every new assert + statement visited and used by the other visitors. + """ + + def __init__( + self, module_path: str | None, config: Config | None, source: bytes + ) -> None: + super().__init__() + self.module_path = module_path + self.config = config + if config is not None: + self.enable_assertion_pass_hook = config.getini( + "enable_assertion_pass_hook" + ) + else: + self.enable_assertion_pass_hook = False + self.source = source + self.scope: tuple[ast.AST, ...] = () + self.variables_overwrite: defaultdict[tuple[ast.AST, ...], dict[str, str]] = ( + defaultdict(dict) + ) + + def run(self, mod: ast.Module) -> None: + """Find all assert statements in *mod* and rewrite them.""" + if not mod.body: + # Nothing to do. + return + + # We'll insert some special imports at the top of the module, but after any + # docstrings and __future__ imports, so first figure out where that is. + doc = getattr(mod, "docstring", None) + expect_docstring = doc is None + if doc is not None and self.is_rewrite_disabled(doc): + return + pos = 0 + for item in mod.body: + 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. + if isinstance(item, ast.FunctionDef) and item.decorator_list: + lineno = item.decorator_list[0].lineno + else: + lineno = item.lineno + # Now actually insert the special imports. + 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 + ] + mod.body[pos:pos] = imports + + # Collect asserts. + self.scope = (mod,) + nodes: list[ast.AST | Sentinel] = [mod] + while nodes: + node = nodes.pop() + if isinstance(node, ast.FunctionDef | ast.AsyncFunctionDef | ast.ClassDef): + self.scope = tuple((*self.scope, node)) + nodes.append(_SCOPE_END_MARKER) + if node == _SCOPE_END_MARKER: + self.scope = self.scope[:-1] + continue + assert isinstance(node, ast.AST) + for name, field in ast.iter_fields(node): + if isinstance(field, list): + new: list[ast.AST] = [] + for i, child in enumerate(field): + if isinstance(child, ast.Assert): + # Transform assert. + new.extend(self.visit(child)) + else: + new.append(child) + if isinstance(child, ast.AST): + nodes.append(child) + setattr(node, name, new) + elif ( + isinstance(field, ast.AST) + # Don't recurse into expressions as they can't contain + # asserts. + and not isinstance(field, ast.expr) + ): + nodes.append(field) + + @staticmethod + def is_rewrite_disabled(docstring: str) -> bool: + return "PYTEST_DONT_REWRITE" in docstring + + def variable(self) -> str: + """Get a new variable.""" + # Use a character invalid in python identifiers to avoid clashing. + name = "@py_assert" + str(next(self.variable_counter)) + self.variables.append(name) + return name + + def assign(self, expr: ast.expr) -> ast.Name: + """Give *expr* a name.""" + name = self.variable() + self.statements.append(ast.Assign([ast.Name(name, ast.Store())], expr)) + return ast.copy_location(ast.Name(name, ast.Load()), expr) + + def display(self, expr: ast.expr) -> ast.expr: + """Call saferepr on the expression.""" + return self.helper("_saferepr", expr) + + def helper(self, name: str, *args: ast.expr) -> ast.expr: + """Call a helper in this module.""" + py_name = ast.Name("@pytest_ar", ast.Load()) + attr = ast.Attribute(py_name, name, ast.Load()) + return ast.Call(attr, list(args), []) + + def builtin(self, name: str) -> ast.Attribute: + """Return the builtin called *name*.""" + builtin_name = ast.Name("@py_builtins", ast.Load()) + return ast.Attribute(builtin_name, name, ast.Load()) + + def explanation_param(self, expr: ast.expr) -> str: + """Return a new named %-formatting placeholder for expr. + + This creates a %-formatting placeholder for expr in the + current formatting context, e.g. ``%(py0)s``. The placeholder + and expr are placed in the current format context so that it + can be used on the next call to .pop_format_context(). + """ + specifier = "py" + str(next(self.variable_counter)) + self.explanation_specifiers[specifier] = expr + return "%(" + specifier + ")s" + + def push_format_context(self) -> None: + """Create a new formatting context. + + The format context is used for when an explanation wants to + have a variable value formatted in the assertion message. In + this case the value required can be added using + .explanation_param(). Finally .pop_format_context() is used + to format a string of %-formatted values as added by + .explanation_param(). + """ + self.explanation_specifiers: dict[str, ast.expr] = {} + self.stack.append(self.explanation_specifiers) + + def pop_format_context(self, expl_expr: ast.expr) -> ast.Name: + """Format the %-formatted string with current format context. + + The expl_expr should be an str ast.expr instance constructed from + the %-placeholders created by .explanation_param(). This will + add the required code to format said string to .expl_stmts and + return the ast.Name instance of the formatted string. + """ + current = self.stack.pop() + if self.stack: + self.explanation_specifiers = self.stack[-1] + keys: list[ast.expr | None] = [ast.Constant(key) for key in current.keys()] + format_dict = ast.Dict(keys, list(current.values())) + form = ast.BinOp(expl_expr, ast.Mod(), format_dict) + name = "@py_format" + str(next(self.variable_counter)) + if self.enable_assertion_pass_hook: + self.format_variables.append(name) + self.expl_stmts.append(ast.Assign([ast.Name(name, ast.Store())], form)) + return ast.Name(name, ast.Load()) + + def generic_visit(self, node: ast.AST) -> tuple[ast.Name, str]: + """Handle expressions we don't have custom code for.""" + assert isinstance(node, ast.expr) + res = self.assign(node) + return res, self.explanation_param(self.display(res)) + + def visit_Assert(self, assert_: ast.Assert) -> list[ast.stmt]: + """Return the AST statements to replace the ast.Assert instance. + + This rewrites the test of an assertion to provide + intermediate values and replace it with an if statement which + raises an assertion error with a detailed explanation in case + the expression is false. + """ + if isinstance(assert_.test, ast.Tuple) and len(assert_.test.elts) >= 1: + import warnings + + from _pytest.warning_types import PytestAssertRewriteWarning + + # TODO: This assert should not be needed. + assert self.module_path is not None + warnings.warn_explicit( + PytestAssertRewriteWarning( + "assertion is always true, perhaps remove parentheses?" + ), + category=None, + filename=self.module_path, + lineno=assert_.lineno, + ) + + self.statements: list[ast.stmt] = [] + self.variables: list[str] = [] + self.variable_counter = itertools.count() + + if self.enable_assertion_pass_hook: + self.format_variables: list[str] = [] + + self.stack: list[dict[str, ast.expr]] = [] + self.expl_stmts: list[ast.stmt] = [] + self.push_format_context() + # Rewrite assert into a bunch of statements. + top_condition, explanation = self.visit(assert_.test) + + negation = ast.UnaryOp(ast.Not(), top_condition) + + if self.enable_assertion_pass_hook: # Experimental pytest_assertion_pass hook + msg = self.pop_format_context(ast.Constant(explanation)) + + # Failed + if assert_.msg: + assertmsg = self.helper("_format_assertmsg", assert_.msg) + gluestr = "\n>assert " + else: + assertmsg = ast.Constant("") + gluestr = "assert " + err_explanation = ast.BinOp(ast.Constant(gluestr), ast.Add(), msg) + err_msg = ast.BinOp(assertmsg, ast.Add(), err_explanation) + err_name = ast.Name("AssertionError", ast.Load()) + fmt = self.helper("_format_explanation", err_msg) + exc = ast.Call(err_name, [fmt], []) + raise_ = ast.Raise(exc, None) + statements_fail = [] + statements_fail.extend(self.expl_stmts) + statements_fail.append(raise_) + + # Passed + fmt_pass = self.helper("_format_explanation", msg) + orig = _get_assertion_exprs(self.source)[assert_.lineno] + hook_call_pass = ast.Expr( + self.helper( + "_call_assertion_pass", + ast.Constant(assert_.lineno), + ast.Constant(orig), + fmt_pass, + ) + ) + # If any hooks implement assert_pass hook + hook_impl_test = ast.If( + self.helper("_check_if_assertion_pass_impl"), + [*self.expl_stmts, hook_call_pass], + [], + ) + statements_pass: list[ast.stmt] = [hook_impl_test] + + # Test for assertion condition + main_test = ast.If(negation, statements_fail, statements_pass) + self.statements.append(main_test) + if self.format_variables: + variables: list[ast.expr] = [ + ast.Name(name, ast.Store()) for name in self.format_variables + ] + clear_format = ast.Assign(variables, ast.Constant(None)) + self.statements.append(clear_format) + + else: # Original assertion rewriting + # Create failure message. + body = self.expl_stmts + self.statements.append(ast.If(negation, body, [])) + if assert_.msg: + assertmsg = self.helper("_format_assertmsg", assert_.msg) + explanation = "\n>assert " + explanation + else: + assertmsg = ast.Constant("") + explanation = "assert " + explanation + template = ast.BinOp(assertmsg, ast.Add(), ast.Constant(explanation)) + msg = self.pop_format_context(template) + fmt = self.helper("_format_explanation", msg) + err_name = ast.Name("AssertionError", ast.Load()) + exc = ast.Call(err_name, [fmt], []) + raise_ = ast.Raise(exc, None) + + body.append(raise_) + + # Clear temporary variables by setting them to None. + if self.variables: + variables = [ast.Name(name, ast.Store()) for name in self.variables] + clear = ast.Assign(variables, ast.Constant(None)) + self.statements.append(clear) + # Fix locations (line numbers/column offsets). + for stmt in self.statements: + for node in traverse_node(stmt): + if getattr(node, "lineno", None) is None: + # apply the assertion location to all generated ast nodes without source location + # and preserve the location of existing nodes or generated nodes with an correct location. + ast.copy_location(node, assert_) + return self.statements + + def visit_NamedExpr(self, name: ast.NamedExpr) -> tuple[ast.NamedExpr, str]: + # This method handles the 'walrus operator' repr of the target + # name if it's a local variable or _should_repr_global_name() + # thinks it's acceptable. + locs = ast.Call(self.builtin("locals"), [], []) + target_id = name.target.id + inlocs = ast.Compare(ast.Constant(target_id), [ast.In()], [locs]) + dorepr = self.helper("_should_repr_global_name", name) + test = ast.BoolOp(ast.Or(), [inlocs, dorepr]) + expr = ast.IfExp(test, self.display(name), ast.Constant(target_id)) + return name, self.explanation_param(expr) + + def visit_Name(self, name: ast.Name) -> tuple[ast.Name, str]: + # Display the repr of the name if it's a local variable or + # _should_repr_global_name() thinks it's acceptable. + locs = ast.Call(self.builtin("locals"), [], []) + inlocs = ast.Compare(ast.Constant(name.id), [ast.In()], [locs]) + dorepr = self.helper("_should_repr_global_name", name) + test = ast.BoolOp(ast.Or(), [inlocs, dorepr]) + expr = ast.IfExp(test, self.display(name), ast.Constant(name.id)) + return name, self.explanation_param(expr) + + def visit_BoolOp(self, boolop: ast.BoolOp) -> tuple[ast.Name, str]: + res_var = self.variable() + expl_list = self.assign(ast.List([], ast.Load())) + app = ast.Attribute(expl_list, "append", ast.Load()) + is_or = int(isinstance(boolop.op, ast.Or)) + body = save = self.statements + fail_save = self.expl_stmts + levels = len(boolop.values) - 1 + self.push_format_context() + # Process each operand, short-circuiting if needed. + for i, v in enumerate(boolop.values): + if i: + fail_inner: list[ast.stmt] = [] + # cond is set in a prior loop iteration below + self.expl_stmts.append(ast.If(cond, fail_inner, [])) # noqa: F821 + self.expl_stmts = fail_inner + 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(ast.Constant(expl)) + call = ast.Call(app, [expl_format], []) + self.expl_stmts.append(ast.Expr(call)) + if i < levels: + cond: ast.expr = res + if is_or: + cond = ast.UnaryOp(ast.Not(), cond) + inner: list[ast.stmt] = [] + self.statements.append(ast.If(cond, inner, [])) + self.statements = body = inner + self.statements = save + self.expl_stmts = fail_save + expl_template = self.helper("_format_boolop", expl_list, ast.Constant(is_or)) + expl = self.pop_format_context(expl_template) + return ast.Name(res_var, ast.Load()), self.explanation_param(expl) + + def visit_UnaryOp(self, unary: ast.UnaryOp) -> tuple[ast.Name, str]: + pattern = UNARY_MAP[unary.op.__class__] + operand_res, operand_expl = self.visit(unary.operand) + res = self.assign(ast.copy_location(ast.UnaryOp(unary.op, operand_res), unary)) + return res, pattern % (operand_expl,) + + def visit_BinOp(self, binop: ast.BinOp) -> tuple[ast.Name, str]: + symbol = BINOP_MAP[binop.op.__class__] + left_expr, left_expl = self.visit(binop.left) + right_expr, right_expl = self.visit(binop.right) + explanation = f"({left_expl} {symbol} {right_expl})" + res = self.assign( + ast.copy_location(ast.BinOp(left_expr, binop.op, right_expr), binop) + ) + return res, explanation + + def visit_Call(self, call: ast.Call) -> tuple[ast.Name, str]: + new_func, func_expl = self.visit(call.func) + arg_expls = [] + new_args = [] + new_kwargs = [] + for arg in call.args: + if isinstance(arg, ast.Name) and arg.id in self.variables_overwrite.get( + self.scope, {} + ): + arg = self.variables_overwrite[self.scope][arg.id] # type:ignore[assignment] + res, expl = self.visit(arg) + arg_expls.append(expl) + new_args.append(res) + for keyword in call.keywords: + 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: + arg_expls.append(keyword.arg + "=" + expl) + else: # **args have `arg` keywords with an .arg of None + arg_expls.append("**" + expl) + + expl = "{}({})".format(func_expl, ", ".join(arg_expls)) + new_call = ast.copy_location(ast.Call(new_func, new_args, new_kwargs), call) + res = self.assign(new_call) + res_expl = self.explanation_param(self.display(res)) + outer_expl = f"{res_expl}\n{{{res_expl} = {expl}\n}}" + return res, outer_expl + + def visit_Starred(self, starred: ast.Starred) -> tuple[ast.Starred, str]: + # A Starred node can appear in a function call. + res, expl = self.visit(starred.value) + new_starred = ast.Starred(res, starred.ctx) + return new_starred, "*" + expl + + def visit_Attribute(self, attr: ast.Attribute) -> tuple[ast.Name, str]: + if not isinstance(attr.ctx, ast.Load): + return self.generic_visit(attr) + value, value_expl = self.visit(attr.value) + res = self.assign( + ast.copy_location(ast.Attribute(value, attr.attr, ast.Load()), attr) + ) + res_expl = self.explanation_param(self.display(res)) + pat = "%s\n{%s = %s.%s\n}" + expl = pat % (res_expl, res_expl, value_expl, attr.attr) + return res, expl + + def visit_Compare(self, comp: ast.Compare) -> tuple[ast.expr, str]: + self.push_format_context() + # We first check if we have overwritten a variable in the previous assert + 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): + left_expl = f"({left_expl})" + res_variables = [self.variable() for i in range(len(comp.ops))] + load_names: list[ast.expr] = [ast.Name(v, ast.Load()) for v in res_variables] + store_names = [ast.Name(v, ast.Store()) for v in res_variables] + it = zip(range(len(comp.ops)), comp.ops, comp.comparators, strict=True) + expls: list[ast.expr] = [] + syms: list[ast.expr] = [] + results = [left_res] + for i, op, next_operand in it: + 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): + next_expl = f"({next_expl})" + results.append(next_res) + sym = BINOP_MAP[op.__class__] + syms.append(ast.Constant(sym)) + expl = f"{left_expl} {sym} {next_expl}" + expls.append(ast.Constant(expl)) + res_expr = ast.copy_location(ast.Compare(left_res, [op], [next_res]), comp) + self.statements.append(ast.Assign([store_names[i]], res_expr)) + left_res, left_expl = next_res, next_expl + # Use pytest.assertion.util._reprcompare if that's available. + expl_call = self.helper( + "_call_reprcompare", + ast.Tuple(syms, ast.Load()), + ast.Tuple(load_names, ast.Load()), + ast.Tuple(expls, ast.Load()), + ast.Tuple(results, ast.Load()), + ) + if len(comp.ops) > 1: + res: ast.expr = ast.BoolOp(ast.And(), load_names) + else: + res = load_names[0] + + return res, self.explanation_param(self.pop_format_context(expl_call)) + + +def try_makedirs(cache_dir: Path) -> bool: + """Attempt to create the given directory and sub-directories exist. + + Returns True if successful or if it already exists. + """ + try: + os.makedirs(cache_dir, exist_ok=True) + except (FileNotFoundError, NotADirectoryError, FileExistsError): + # One of the path components was not a directory: + # - we're in a zip file + # - it is a file + return False + except PermissionError: + return False + except OSError as e: + # as of now, EROFS doesn't have an equivalent OSError-subclass + # + # squashfuse_ll returns ENOSYS "OSError: [Errno 38] Function not + # implemented" for a read-only error + if e.errno in {errno.EROFS, errno.ENOSYS}: + return False + raise + return True + + +def get_cache_dir(file_path: Path) -> Path: + """Return the cache directory to write .pyc files for the given .py file path.""" + if sys.pycache_prefix: + # given: + # prefix = '/tmp/pycs' + # path = '/home/user/proj/test_app.py' + # we want: + # '/tmp/pycs/home/user/proj' + return Path(sys.pycache_prefix) / Path(*file_path.parts[1:-1]) + else: + # classic pycache directory + return file_path.parent / "__pycache__" diff --git a/py311/lib/python3.11/site-packages/_pytest/assertion/truncate.py b/py311/lib/python3.11/site-packages/_pytest/assertion/truncate.py new file mode 100644 index 0000000000000000000000000000000000000000..5820e6e8a80e3ed2479fe54b018c85c5114dfbb4 --- /dev/null +++ b/py311/lib/python3.11/site-packages/_pytest/assertion/truncate.py @@ -0,0 +1,137 @@ +"""Utilities for truncating assertion output. + +Current default behaviour is to truncate assertion explanations at +terminal lines, unless running with an assertions verbosity level of at least 2 or running on CI. +""" + +from __future__ import annotations + +from _pytest.compat import running_on_ci +from _pytest.config import Config +from _pytest.nodes import Item + + +DEFAULT_MAX_LINES = 8 +DEFAULT_MAX_CHARS = DEFAULT_MAX_LINES * 80 +USAGE_MSG = "use '-vv' to show" + + +def truncate_if_required(explanation: list[str], item: Item) -> list[str]: + """Truncate this assertion explanation if the given test item is eligible.""" + 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 _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: int, + max_chars: int, +) -> list[str]: + """Truncate given list of strings that makes up the assertion explanation. + + 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. + """ + # Check if truncation required + input_char_count = len("".join(input_lines)) + # The length of the truncation explanation depends on the number of lines + # removed but is at least 68 characters: + # The real value is + # 64 (for the base message: + # '...\n...Full output truncated (1 line hidden), use '-vv' to show")' + # ) + # + 1 (for plural) + # + int(math.log10(len(input_lines) - max_lines)) (number of hidden line, at least 1) + # + 3 for the '...' added to the truncated line + # But if there's more than 100 lines it's very likely that we're going to + # truncate, so we don't need the exact value using log10. + tolerable_max_chars = ( + max_chars + 70 # 64 + 1 (for plural) + 2 (for '99') + 3 for '...' + ) + # The truncation explanation add two lines to the output + tolerable_max_lines = max_lines + 2 + if ( + len(input_lines) <= tolerable_max_lines + and input_char_count <= tolerable_max_chars + ): + return input_lines + # Truncate first to max_lines, and then truncate to max_chars if necessary + 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 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 + truncated_explanation[-1] = truncated_explanation[-1] + "..." + if truncated_char: + # It's possible that we did not remove any char from this line + truncated_line_count += 1 + else: + # Add proper ellipsis when we were able to fit a full line exactly + truncated_explanation[-1] = "..." + return [ + *truncated_explanation, + "", + f"...Full output truncated ({truncated_line_count} line" + f"{'' if truncated_line_count == 1 else 's'} hidden), {USAGE_MSG}", + ] + + +def _truncate_by_char_count(input_lines: list[str], max_chars: int) -> list[str]: + # Find point at which input length exceeds total allowed length + iterated_char_count = 0 + for iterated_index, input_line in enumerate(input_lines): + if iterated_char_count + len(input_line) > max_chars: + break + iterated_char_count += len(input_line) + + # Create truncated explanation with modified final line + truncated_result = input_lines[:iterated_index] + final_line = input_lines[iterated_index] + if final_line: + final_line_truncate_point = max_chars - iterated_char_count + final_line = final_line[:final_line_truncate_point] + truncated_result.append(final_line) + return truncated_result diff --git a/py311/lib/python3.11/site-packages/_pytest/assertion/util.py b/py311/lib/python3.11/site-packages/_pytest/assertion/util.py new file mode 100644 index 0000000000000000000000000000000000000000..f35d83a6fe4aa38acc747ff5dbdaf96adab51178 --- /dev/null +++ b/py311/lib/python3.11/site-packages/_pytest/assertion/util.py @@ -0,0 +1,615 @@ +# mypy: allow-untyped-defs +"""Utilities for assertion debugging.""" + +from __future__ import annotations + +import collections.abc +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 Any +from typing import Literal +from typing import Protocol +from unicodedata import normalize + +from _pytest import outcomes +import _pytest._code +from _pytest._io.pprint import PrettyPrinter +from _pytest._io.saferepr import saferepr +from _pytest._io.saferepr import saferepr_unlimited +from _pytest.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: Callable[[str, object, object], str | None] | None = None + +# Works similarly as _reprcompare attribute. Is populated with the hook call +# when pytest_runtest_setup is called. +_assertion_pass: Callable[[int, str, str], None] | None = None + +# Config object which is assigned during pytest_runtest_protocol. +_config: Config | None = None + + +class _HighlightFunc(Protocol): + def __call__(self, source: str, lexer: Literal["diff", "python"] = "python") -> str: + """Apply highlighting to the given source.""" + + +def 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: + r"""Format an explanation. + + Normally all embedded newlines are escaped, however there are + three exceptions: \n{, \n} and \n~. The first two are intended + cover nested explanations, see function and attribute explanations + for examples (.visit_Call(), visit_Attribute()). The last one is + for when one explanation needs to span multiple lines, e.g. when + displaying diffs. + """ + lines = _split_explanation(explanation) + result = _format_lines(lines) + return "\n".join(result) + + +def _split_explanation(explanation: str) -> list[str]: + r"""Return a list of individual lines in the explanation. + + This will return a list of lines split on '\n{', '\n}' and '\n~'. + Any other newlines will be escaped and appear in the line as the + literal '\n' characters. + """ + raw_lines = (explanation or "").split("\n") + lines = [raw_lines[0]] + for values in raw_lines[1:]: + if values and values[0] in ["{", "}", "~", ">"]: + lines.append(values) + else: + lines[-1] += "\\n" + values + return lines + + +def _format_lines(lines: Sequence[str]) -> list[str]: + """Format the individual lines. + + This will replace the '{', '}' and '~' characters of our mini formatting + language with the proper 'where ...', 'and ...' and ' + ...' text, taking + care of indentation along the way. + + Return a list of formatted lines. + """ + result = list(lines[:1]) + stack = [0] + stackcnt = [0] + for line in lines[1:]: + if line.startswith("{"): + if stackcnt[-1]: + s = "and " + else: + s = "where " + stack.append(len(result)) + stackcnt[-1] += 1 + stackcnt.append(0) + result.append(" +" + " " * (len(stack) - 1) + s + line[1:]) + elif line.startswith("}"): + stack.pop() + stackcnt.pop() + result[stack[-1]] += line[1:] + else: + assert line[0] in ["~", ">"] + stack[-1] += 1 + indent = len(stack) if line.startswith("~") else len(stack) - 1 + result.append(" " * indent + line[1:]) + assert len(stack) == 1 + return result + + +def issequence(x: Any) -> bool: + return isinstance(x, collections.abc.Sequence) and not isinstance(x, str) + + +def istext(x: Any) -> bool: + return isinstance(x, str) + + +def isdict(x: Any) -> bool: + return isinstance(x, dict) + + +def isset(x: Any) -> bool: + return isinstance(x, set | frozenset) + + +def isnamedtuple(obj: Any) -> bool: + return isinstance(obj, tuple) and getattr(obj, "_fields", None) is not None + + +def isdatacls(obj: Any) -> bool: + return getattr(obj, "__dataclass_fields__", None) is not None + + +def isattrs(obj: Any) -> bool: + return getattr(obj, "__attrs_attrs__", None) is not None + + +def isiterable(obj: Any) -> bool: + try: + iter(obj) + return not istext(obj) + except Exception: + return False + + +def has_default_eq( + obj: object, +) -> bool: + """Check if an instance of an object contains the default eq + + First, we check if the object's __eq__ attribute has __code__, + if so, we check the equally of the method code filename (__code__.co_filename) + to the default one generated by the dataclass and attr module + for dataclasses the default co_filename is , for attrs class, the __eq__ should contain "attrs eq generated" + """ + # inspired from https://github.com/willmcgugan/rich/blob/07d51ffc1aee6f16bd2e5a25b4e82850fb9ed778/rich/pretty.py#L68 + if hasattr(obj.__eq__, "__code__") and hasattr(obj.__eq__.__code__, "co_filename"): + code_filename = obj.__eq__.__code__.co_filename + + if isattrs(obj): + return "attrs generated " in code_filename + + return code_filename == "" # data class + return True + + +def assertrepr_compare( + config, op: str, left: Any, right: Any, use_ascii: bool = False +) -> list[str] | None: + """Return specialised explanations for some operators/operands.""" + verbose = config.get_verbosity(Config.VERBOSITY_ASSERTIONS) + + # Strings which normalize equal are often hard to distinguish when printed; use ascii() to make this easier. + # See issue #3246. + use_ascii = ( + isinstance(left, str) + and isinstance(right, str) + and normalize("NFD", left) == normalize("NFD", right) + ) + + if verbose > 1: + left_repr = saferepr_unlimited(left, use_ascii=use_ascii) + right_repr = saferepr_unlimited(right, use_ascii=use_ascii) + else: + # XXX: "15 chars indentation" is wrong + # ("E AssertionError: assert "); should use term width. + maxsize = ( + 80 - 15 - len(op) - 2 + ) // 2 # 15 chars indentation, 1 space around op + + left_repr = saferepr(left, maxsize=maxsize, use_ascii=use_ascii) + right_repr = saferepr(right, maxsize=maxsize, use_ascii=use_ascii) + + summary = f"{left_repr} {op} {right_repr}" + highlighter = config.get_terminal_writer()._highlight + + explanation = None + try: + if op == "==": + explanation = _compare_eq_any(left, right, highlighter, verbose) + elif op == "not in": + if istext(left) and istext(right): + explanation = _notin_text(left, right, verbose) + elif op == "!=": + if isset(left) and isset(right): + explanation = ["Both sets are equal"] + elif op == ">=": + if isset(left) and isset(right): + explanation = _compare_gte_set(left, right, highlighter, verbose) + elif op == "<=": + if isset(left) and isset(right): + explanation = _compare_lte_set(left, right, highlighter, verbose) + elif op == ">": + if isset(left) and isset(right): + explanation = _compare_gt_set(left, right, highlighter, verbose) + elif op == "<": + if isset(left) and isset(right): + explanation = _compare_lt_set(left, right, highlighter, verbose) + + except outcomes.Exit: + raise + except Exception: + repr_crash = _pytest._code.ExceptionInfo.from_current()._getreprcrash() + explanation = [ + f"(pytest_assertion plugin: representation of details failed: {repr_crash}.", + " Probably an object has a faulty __repr__.)", + ] + + if not explanation: + return None + + if explanation[0] != "": + explanation = ["", *explanation] + return [summary, *explanation] + + +def _compare_eq_any( + left: Any, right: Any, highlighter: _HighlightFunc, verbose: int = 0 +) -> list[str]: + explanation = [] + if istext(left) and istext(right): + explanation = _diff_text(left, right, highlighter, verbose) + else: + from _pytest.python_api import ApproxBase + + if isinstance(left, ApproxBase) or isinstance(right, ApproxBase): + # Although the common order should be obtained == expected, this ensures both ways + approx_side = left if isinstance(left, ApproxBase) else right + other_side = right if isinstance(left, ApproxBase) else left + + explanation = approx_side._repr_compare(other_side) + elif type(left) is type(right) and ( + isdatacls(left) or isattrs(left) or isnamedtuple(left) + ): + # Note: unlike dataclasses/attrs, namedtuples compare only the + # field values, not the type or field names. But this branch + # intentionally only handles the same-type case, which was often + # used in older code bases before dataclasses/attrs were available. + explanation = _compare_eq_cls(left, right, highlighter, verbose) + elif issequence(left) and issequence(right): + explanation = _compare_eq_sequence(left, right, highlighter, verbose) + elif isset(left) and isset(right): + explanation = _compare_eq_set(left, right, highlighter, verbose) + elif isdict(left) and isdict(right): + explanation = _compare_eq_dict(left, right, highlighter, verbose) + + if isiterable(left) and isiterable(right): + expl = _compare_eq_iterable(left, right, highlighter, verbose) + explanation.extend(expl) + + return explanation + + +def _diff_text( + left: str, right: str, 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 + characters which are identical to keep the diff minimal. + """ + from difflib import ndiff + + explanation: list[str] = [] + + if verbose < 1: + i = 0 # just in case left or right has zero length + for i in range(min(len(left), len(right))): + if left[i] != right[i]: + break + if i > 42: + i -= 10 # Provide some context + explanation = [ + f"Skipping {i} identical leading characters in diff, use -v to show" + ] + left = left[i:] + right = right[i:] + if len(left) == len(right): + for i in range(len(left)): + if left[-i] != right[-i]: + break + if i > 42: + i -= 10 # Provide some context + explanation += [ + f"Skipping {i} identical trailing " + "characters in diff, use -v to show" + ] + left = left[:-i] + right = right[:-i] + keepends = True + if left.isspace() or right.isspace(): + left = repr(str(left)) + right = repr(str(right)) + explanation += ["Strings contain only whitespace, escaping them using repr()"] + # "right" is the expected base against which we compare "left", + # see https://github.com/pytest-dev/pytest/issues/3333 + explanation.extend( + highlighter( + "\n".join( + line.strip("\n") + for line in ndiff(right.splitlines(keepends), left.splitlines(keepends)) + ), + lexer="diff", + ).splitlines() + ) + return explanation + + +def _compare_eq_iterable( + left: Iterable[Any], + right: Iterable[Any], + highlighter: _HighlightFunc, + verbose: int = 0, +) -> list[str]: + if verbose <= 0 and not running_on_ci(): + return ["Use -v to get more diff"] + # dynamic import to speedup pytest + import difflib + + left_formatting = PrettyPrinter().pformat(left).splitlines() + right_formatting = PrettyPrinter().pformat(right).splitlines() + + explanation = ["", "Full diff:"] + # "right" is the expected base against which we compare "left", + # see https://github.com/pytest-dev/pytest/issues/3333 + explanation.extend( + highlighter( + "\n".join( + line.rstrip() + for line in difflib.ndiff(right_formatting, left_formatting) + ), + lexer="diff", + ).splitlines() + ) + return explanation + + +def _compare_eq_sequence( + left: Sequence[Any], + right: Sequence[Any], + highlighter: _HighlightFunc, + verbose: int = 0, +) -> list[str]: + comparing_bytes = isinstance(left, bytes) and isinstance(right, bytes) + explanation: list[str] = [] + len_left = len(left) + len_right = len(right) + for i in range(min(len_left, len_right)): + if left[i] != right[i]: + if comparing_bytes: + # when comparing bytes, we want to see their ascii representation + # instead of their numeric values (#5260) + # using a slice gives us the ascii representation: + # >>> s = b'foo' + # >>> s[0] + # 102 + # >>> s[0:1] + # b'f' + left_value = left[i : i + 1] + right_value = right[i : i + 1] + else: + left_value = left[i] + right_value = right[i] + + explanation.append( + f"At index {i} diff:" + f" {highlighter(repr(left_value))} != {highlighter(repr(right_value))}" + ) + break + + if comparing_bytes: + # when comparing bytes, it doesn't help to show the "sides contain one or more + # items" longer explanation, so skip it + + return explanation + + len_diff = len_left - len_right + if len_diff: + if len_diff > 0: + dir_with_more = "Left" + extra = saferepr(left[len_right]) + else: + len_diff = 0 - len_diff + dir_with_more = "Right" + extra = saferepr(right[len_left]) + + if len_diff == 1: + explanation += [ + f"{dir_with_more} contains one more item: {highlighter(extra)}" + ] + else: + explanation += [ + 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], + highlighter: _HighlightFunc, + verbose: int = 0, +) -> list[str]: + explanation = [] + explanation.extend(_set_one_sided_diff("left", left, right, highlighter)) + explanation.extend(_set_one_sided_diff("right", right, left, highlighter)) + return explanation + + +def _compare_gt_set( + left: AbstractSet[Any], + right: AbstractSet[Any], + highlighter: _HighlightFunc, + verbose: int = 0, +) -> list[str]: + explanation = _compare_gte_set(left, right, highlighter) + if not explanation: + return ["Both sets are equal"] + return explanation + + +def _compare_lt_set( + left: AbstractSet[Any], + right: AbstractSet[Any], + highlighter: _HighlightFunc, + verbose: int = 0, +) -> list[str]: + explanation = _compare_lte_set(left, right, highlighter) + if not explanation: + return ["Both sets are equal"] + return explanation + + +def _compare_gte_set( + left: AbstractSet[Any], + right: AbstractSet[Any], + highlighter: _HighlightFunc, + verbose: int = 0, +) -> list[str]: + return _set_one_sided_diff("right", right, left, highlighter) + + +def _compare_lte_set( + left: AbstractSet[Any], + right: AbstractSet[Any], + highlighter: _HighlightFunc, + verbose: int = 0, +) -> list[str]: + return _set_one_sided_diff("left", left, right, highlighter) + + +def _set_one_sided_diff( + posn: str, + set1: AbstractSet[Any], + set2: AbstractSet[Any], + highlighter: _HighlightFunc, +) -> list[str]: + explanation = [] + diff = set1 - set2 + if diff: + explanation.append(f"Extra items in the {posn} set:") + for item in diff: + explanation.append(highlighter(saferepr(item))) + return explanation + + +def _compare_eq_dict( + left: Mapping[Any, Any], + right: Mapping[Any, Any], + highlighter: _HighlightFunc, + verbose: int = 0, +) -> list[str]: + explanation: list[str] = [] + set_left = set(left) + set_right = set(right) + common = set_left.intersection(set_right) + same = {k: left[k] for k in common if left[k] == right[k]} + if same and verbose < 2: + explanation += [f"Omitting {len(same)} identical items, use -vv to show"] + elif same: + explanation += ["Common items:"] + explanation += highlighter(pprint.pformat(same)).splitlines() + diff = {k for k in common if left[k] != right[k]} + if diff: + explanation += ["Differing items:"] + for k in diff: + explanation += [ + highlighter(saferepr({k: left[k]})) + + " != " + + highlighter(saferepr({k: right[k]})) + ] + extra_left = set_left - set_right + len_extra_left = len(extra_left) + if len_extra_left: + explanation.append( + f"Left contains {len_extra_left} more item{'' if len_extra_left == 1 else 's'}:" + ) + explanation.extend( + highlighter(pprint.pformat({k: left[k] for k in extra_left})).splitlines() + ) + extra_right = set_right - set_left + len_extra_right = len(extra_right) + if len_extra_right: + explanation.append( + f"Right contains {len_extra_right} more item{'' if len_extra_right == 1 else 's'}:" + ) + explanation.extend( + highlighter(pprint.pformat({k: right[k] for k in extra_right})).splitlines() + ) + return explanation + + +def _compare_eq_cls( + left: Any, right: Any, highlighter: _HighlightFunc, verbose: int +) -> list[str]: + if not has_default_eq(left): + return [] + if isdatacls(left): + import dataclasses + + all_fields = dataclasses.fields(left) + fields_to_check = [info.name for info in all_fields if info.compare] + elif isattrs(left): + all_fields = left.__attrs_attrs__ + fields_to_check = [field.name for field in all_fields if getattr(field, "eq")] + elif isnamedtuple(left): + fields_to_check = left._fields + else: + assert False + + indent = " " + same = [] + diff = [] + for field in fields_to_check: + if getattr(left, field) == getattr(right, field): + same.append(field) + else: + diff.append(field) + + explanation = [] + if same or diff: + explanation += [""] + if same and verbose < 2: + explanation.append(f"Omitting {len(same)} identical items, use -vv to show") + elif same: + explanation += ["Matching attributes:"] + explanation += highlighter(pprint.pformat(same)).splitlines() + if diff: + explanation += ["Differing attributes:"] + explanation += highlighter(pprint.pformat(diff)).splitlines() + for field in diff: + field_left = getattr(left, field) + field_right = getattr(right, field) + explanation += [ + "", + f"Drill down into differing attribute {field}:", + f"{indent}{field}: {highlighter(repr(field_left))} != {highlighter(repr(field_right))}", + ] + explanation += [ + indent + line + for line in _compare_eq_any( + field_left, field_right, highlighter, verbose + ) + ] + return explanation + + +def _notin_text(term: str, text: str, verbose: int = 0) -> list[str]: + index = text.find(term) + head = text[:index] + tail = text[index + len(term) :] + correct_text = head + tail + diff = _diff_text(text, correct_text, dummy_highlighter, verbose) + newdiff = [f"{saferepr(term, maxsize=42)} is contained here:"] + for line in diff: + if line.startswith("Skipping"): + continue + if line.startswith("- "): + continue + if line.startswith("+ "): + newdiff.append(" " + line[2:]) + else: + newdiff.append(line) + return newdiff diff --git a/py311/lib/python3.11/site-packages/_pytest/config/__init__.py b/py311/lib/python3.11/site-packages/_pytest/config/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..6b02e160e1a8dad2f25b98ae3cc8872a213a9e88 --- /dev/null +++ b/py311/lib/python3.11/site-packages/_pytest/config/__init__.py @@ -0,0 +1,2197 @@ +# 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 +from textwrap import dedent +import types +from types import FunctionType +from typing import Any +from typing import cast +from typing import Final +from typing import final +from typing import IO +from typing import TextIO +from typing import TYPE_CHECKING +import warnings + +import pluggy +from pluggy import HookimplMarker +from pluggy import HookimplOpts +from pluggy import HookspecMarker +from pluggy import HookspecOpts +from pluggy import PluginManager + +from .compat import PathAwareHookProxy +from .exceptions import PrintHelp as PrintHelp +from .exceptions import UsageError as UsageError +from .findpaths import 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 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 +from _pytest.pathlib import bestrelpath +from _pytest.pathlib import import_path +from _pytest.pathlib import ImportMode +from _pytest.pathlib import resolve_package_path +from _pytest.pathlib import safe_exists +from _pytest.stash import Stash +from _pytest.warning_types import PytestConfigWarning +from _pytest.warning_types import warn_explicit_for + + +if TYPE_CHECKING: + from _pytest.assertion.rewrite import AssertionRewritingHook + from _pytest.cacheprovider import Cache + from _pytest.terminal import TerminalReporter + +_PluggyPlugin = object +"""A type to represent plugin objects. + +Plugins can be any namespace, so we can't narrow it down much, but we use an +alias to make the intent clear. + +Ideally this type would be provided by pluggy itself. +""" + + +hookimpl = HookimplMarker("pytest") +hookspec = HookspecMarker("pytest") + + +@final +class ExitCode(enum.IntEnum): + """Encodes the valid exit codes by pytest. + + Currently users and plugins may supply other exit codes as well. + + .. versionadded:: 5.0 + """ + + #: Tests passed. + OK = 0 + #: Tests failed. + TESTS_FAILED = 1 + #: pytest was interrupted. + INTERRUPTED = 2 + #: An internal error got in the way. + INTERNAL_ERROR = 3 + #: pytest was misused. + USAGE_ERROR = 4 + #: pytest couldn't find tests. + NO_TESTS_COLLECTED = 5 + + __module__ = "pytest" + + +class ConftestImportFailure(Exception): + def __init__( + self, + path: pathlib.Path, + *, + cause: Exception, + ) -> None: + self.path = path + self.cause = cause + + def __str__(self) -> str: + return f"{type(self.cause).__name__}: {self.cause} (from {self.path})" + + +def filter_traceback_for_conftest_import_failure( + entry: _pytest._code.TracebackEntry, +) -> bool: + """Filter tracebacks entries which point to pytest internals or importlib. + + Make a special case for importlib because we use it to import test modules and conftest files + in _pytest.pathlib.import_path. + """ + return filter_traceback(entry) and "importlib" not in str(entry.path).split(os.sep) + + +def 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: list[str] | os.PathLike[str] | None = None, + plugins: Sequence[str | _PluggyPlugin] | None = None, +) -> int | ExitCode: + """Perform an in-process test run. + + :param args: + List of command line arguments. If `None` or not given, defaults to reading + arguments directly from the process command line (:data:`sys.argv`). + :param plugins: List of plugin objects to be auto-registered during initialization. + + :returns: An exit code. + """ + # 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(new_args, plugins) + except ConftestImportFailure as e: + print_conftest_import_error(e, file=sys.stderr) + return ExitCode.USAGE_ERROR + + try: + ret: ExitCode | int = config.hook.pytest_cmdline_main(config=config) + try: + return ExitCode(ret) + except ValueError: + return ret + finally: + config._ensure_unconfigure() + except UsageError as e: + 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: + """The CLI entry point of pytest. + + This function is not meant for programmable use; use `main()` instead. + """ + # https://docs.python.org/3/library/signal.html#note-on-sigpipe + try: + code = main() + sys.stdout.flush() + return code + except BrokenPipeError: + # Python flushes standard streams on exit; redirect remaining output + # to devnull to avoid another BrokenPipeError at shutdown + devnull = os.open(os.devnull, os.O_WRONLY) + os.dup2(devnull, sys.stdout.fileno()) + return 1 # Python exits with error code 1 on EPIPE + + +class cmdline: # compatibility namespace + main = staticmethod(main) + + +def filename_arg(path: str, optname: str) -> str: + """Argparse type validator for filename arguments. + + :path: Path of filename. + :optname: Name of the option. + """ + if os.path.isdir(path): + raise UsageError(f"{optname} must be a filename, given: {path}") + return path + + +def directory_arg(path: str, optname: str) -> str: + """Argparse type validator for directory arguments. + + :path: Path of directory. + :optname: Name of the option. + """ + if not os.path.isdir(path): + raise UsageError(f"{optname} must be a directory, given: {path}") + return path + + +# Plugins that cannot be disabled via "-p no:X" currently. +essential_plugins = ( + "mark", + "main", + "runner", + "fixtures", + "helpconfig", # Provides -p. +) + +default_plugins = ( + *essential_plugins, + "python", + "terminal", + "debugging", + "unittest", + "capture", + "skipping", + "legacypath", + "tmpdir", + "monkeypatch", + "recwarn", + "pastebin", + "assertion", + "junitxml", + "doctest", + "cacheprovider", + "setuponly", + "setupplan", + "stepwise", + "unraisableexception", + "threadexception", + "warnings", + "logging", + "reports", + "faulthandler", + "subtests", +) + +builtin_plugins = { + *default_plugins, + "pytester", + "pytester_assertions", + "terminalprogress", +} + + +def get_config( + args: Iterable[str] | None = None, + plugins: Sequence[str | _PluggyPlugin] | None = None, +) -> Config: + # Subsequent calls to main will create a fresh instance. + pluginmanager = PytestPluginManager() + invocation_params = Config.InvocationParams( + args=args or (), + plugins=plugins, + dir=pathlib.Path.cwd(), + ) + config = Config(pluginmanager, invocation_params=invocation_params) + + if invocation_params.args: + # Handle any "-p no:plugin" args. + pluginmanager.consider_preparse(invocation_params.args, exclude_only=True) + + for spec in default_plugins: + pluginmanager.import_plugin(spec) + + return config + + +def get_plugin_manager() -> PytestPluginManager: + """Obtain a new instance of the + :py:class:`pytest.PytestPluginManager`, with default plugins + already loaded. + + This function can be used by integration with other tools, like hooking + into pytest to run tests into an IDE. + """ + return get_config().pluginmanager + + +def _prepareconfig( + args: list[str] | os.PathLike[str], + 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] + "`args` parameter expected to be a list of strings, got: {!r} (type: {})" + ) + raise TypeError(msg.format(args, type(args))) + + initial_config = get_config(args, plugins) + pluginmanager = initial_config.pluginmanager + try: + if plugins: + for plugin in plugins: + if isinstance(plugin, str): + pluginmanager.consider_pluginarg(plugin) + else: + pluginmanager.register(plugin) + config: Config = pluginmanager.hook.pytest_cmdline_parse( + pluginmanager=pluginmanager, args=args + ) + return config + except BaseException: + initial_config._ensure_unconfigure() + raise + + +def _get_directory(path: pathlib.Path) -> pathlib.Path: + """Get the directory of a path - itself if already a directory.""" + if path.is_file(): + return path.parent + else: + return path + + +def _get_legacy_hook_marks( + method: Any, + hook_type: str, + opt_names: tuple[str, ...], +) -> dict[str, bool]: + if TYPE_CHECKING: + # abuse typeguard from importlib to avoid massive method type union that's lacking an alias + assert inspect.isroutine(method) + known_marks: set[str] = {m.name for m in getattr(method, "pytestmark", [])} + must_warn: list[str] = [] + opts: dict[str, bool] = {} + for opt_name in opt_names: + opt_attr = getattr(method, opt_name, AttributeError) + if opt_attr is not AttributeError: + must_warn.append(f"{opt_name}={opt_attr}") + opts[opt_name] = True + elif opt_name in known_marks: + must_warn.append(f"{opt_name}=True") + opts[opt_name] = True + else: + opts[opt_name] = False + if must_warn: + hook_opts = ", ".join(must_warn) + message = _pytest.deprecated.HOOK_LEGACY_MARKING.format( + type=hook_type, + fullname=method.__qualname__, + hook_opts=hook_opts, + ) + warn_explicit_for(cast(FunctionType, method), message) + return opts + + +@final +class PytestPluginManager(PluginManager): + """A :py:class:`pluggy.PluginManager ` with + additional pytest-specific functionality: + + * Loading plugins from the command line, ``PYTEST_PLUGINS`` env variable and + ``pytest_plugins`` global variables found in plugins being loaded. + * ``conftest.py`` loading during start-up. + """ + + def __init__(self) -> None: + 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() + # All conftest modules applicable for a directory. + # This includes the directory's own conftest modules as well + # as those of its parent directories. + self._dirpath2confmods: dict[pathlib.Path, list[types.ModuleType]] = {} + # Cutoff directory above which conftests are no longer discovered. + self._confcutdir: pathlib.Path | None = None + # If set, conftest loading is skipped. + self._noconftest = False + + # _getconftestmodules()'s call to _get_directory() causes a stat + # storm when it's called potentially thousands of times in a test + # session (#9478), often with the same path, so cache it. + self._get_directory = lru_cache(256)(_get_directory) + + # plugins that were explicitly skipped with pytest.skip + # list of (module name, skip reason) + # previously we would issue a warning when a plugin was skipped, but + # since we refactored warnings as first citizens of Config, they are + # just stored here to be used later. + self.skipped_plugins: list[tuple[str, str]] = [] + + self.add_hookspecs(_pytest.hookspec) + self.register(self) + if os.environ.get("PYTEST_DEBUG"): + err: IO[str] = sys.stderr + encoding: str = getattr(err, "encoding", "utf8") + try: + err = open( + os.dup(err.fileno()), + mode=err.mode, + buffering=1, + encoding=encoding, + ) + except Exception: + pass + self.trace.root.setwriter(err.write) + self.enable_tracing() + + # Config._consider_importhook will set a real object if required. + self.rewrite_hook: 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 + ) -> HookimplOpts | None: + """:meta private:""" + # pytest hooks are always prefixed with "pytest_", + # so we avoid accessing possibly non-readable attributes + # (see issue #1073). + if not name.startswith("pytest_"): + return None + # Ignore names which cannot be hooks. + if name == "pytest_plugins": + return None + + opts = super().parse_hookimpl_opts(plugin, name) + if opts is not None: + return opts + + method = getattr(plugin, name) + # Consider only actual functions for hooks (#3775). + if not inspect.isroutine(method): + return None + # Collect unmarked hooks as long as they have the `pytest_' prefix. + 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) -> 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_"): + legacy = _get_legacy_hook_marks( + method, "spec", ("firstresult", "historic") + ) + opts = cast(HookspecOpts, legacy) + return opts + + def register(self, plugin: _PluggyPlugin, name: str | None = None) -> str | None: + if name in _pytest.deprecated.DEPRECATED_EXTERNAL_PLUGINS: + warnings.warn( + PytestConfigWarning( + "{} plugin has been merged into the core, " + "please remove it from your requirements.".format( + name.replace("_", "-") + ) + ) + ) + return None + plugin_name = super().register(plugin, name) + if plugin_name is not None: + self.hook.pytest_plugin_registered.call_historic( + kwargs=dict( + plugin=plugin, + plugin_name=plugin_name, + manager=self, + ) + ) + + if isinstance(plugin, types.ModuleType): + self.consider_module(plugin) + return plugin_name + + def getplugin(self, name: str): + # Support deprecated naming because plugins (xdist e.g.) use it. + plugin: _PluggyPlugin | None = self.get_plugin(name) + return plugin + + def hasplugin(self, name: str) -> bool: + """Return whether a plugin with the given name is registered.""" + return bool(self.get_plugin(name)) + + def pytest_configure(self, config: Config) -> None: + """:meta private:""" + # XXX now that the pluginmanager exposes hookimpl(tryfirst...) + # we should remove tryfirst/trylast as markers. + config.addinivalue_line( + "markers", + "tryfirst: mark a hook implementation function such that the " + "plugin machinery will try to call it first/as early as possible. " + "DEPRECATED, use @pytest.hookimpl(tryfirst=True) instead.", + ) + config.addinivalue_line( + "markers", + "trylast: mark a hook implementation function such that the " + "plugin machinery will try to call it last/as late as possible. " + "DEPRECATED, use @pytest.hookimpl(trylast=True) instead.", + ) + self._configured = True + + # + # Internal API for local conftest plugin handling. + # + def _set_initial_conftests( + self, + args: Sequence[str | pathlib.Path], + pyargs: bool, + noconftest: bool, + rootpath: pathlib.Path, + confcutdir: pathlib.Path | None, + invocation_dir: pathlib.Path, + importmode: ImportMode | str, + *, + consider_namespace_packages: bool, + ) -> None: + """Load initial conftest files given a preparsed "namespace". + + As conftest files may add their own command line options which have + arguments ('--my-opt somepath') we might get some false positives. + All builtin and 3rd party plugins will have been loaded, however, so + common options will not confuse our logic here. + """ + self._confcutdir = ( + absolutepath(invocation_dir / confcutdir) if confcutdir else None + ) + self._noconftest = noconftest + self._using_pyargs = pyargs + foundanchor = False + for initial_path in args: + path = str(initial_path) + # remove node-id syntax + i = path.find("::") + if i != -1: + path = path[:i] + anchor = absolutepath(invocation_dir / path) + + # Ensure we do not break if what appears to be an anchor + # is in fact a very long option (#10169, #11394). + if safe_exists(anchor): + self._try_load_conftest( + anchor, + importmode, + rootpath, + consider_namespace_packages=consider_namespace_packages, + ) + foundanchor = True + if not foundanchor: + self._try_load_conftest( + invocation_dir, + importmode, + rootpath, + consider_namespace_packages=consider_namespace_packages, + ) + + def _is_in_confcutdir(self, path: pathlib.Path) -> bool: + """Whether to consider the given path to load conftests from.""" + if self._confcutdir is None: + return True + # The semantics here are literally: + # Do not load a conftest if it is found upwards from confcut dir. + # But this is *not* the same as: + # Load only conftests from confcutdir or below. + # At first glance they might seem the same thing, however we do support use cases where + # we want to load conftests that are not found in confcutdir or below, but are found + # in completely different directory hierarchies like packages installed + # in out-of-source trees. + # (see #9767 for a regression where the logic was inverted). + return path not in self._confcutdir.parents + + def _try_load_conftest( + self, + anchor: pathlib.Path, + importmode: str | ImportMode, + rootpath: pathlib.Path, + *, + consider_namespace_packages: bool, + ) -> None: + self._loadconftestmodules( + anchor, + importmode, + rootpath, + consider_namespace_packages=consider_namespace_packages, + ) + # let's also consider test* subdirs + if anchor.is_dir(): + for x in anchor.glob("test*"): + if x.is_dir(): + self._loadconftestmodules( + x, + importmode, + rootpath, + consider_namespace_packages=consider_namespace_packages, + ) + + def _loadconftestmodules( + self, + path: pathlib.Path, + importmode: str | ImportMode, + rootpath: pathlib.Path, + *, + consider_namespace_packages: bool, + ) -> None: + if self._noconftest: + return + + directory = self._get_directory(path) + + # Optimization: avoid repeated searches in the same directory. + # Assumes always called with same importmode and rootpath. + if directory in self._dirpath2confmods: + return + + clist = [] + for parent in reversed((directory, *directory.parents)): + if self._is_in_confcutdir(parent): + conftestpath = parent / "conftest.py" + if conftestpath.is_file(): + mod = self._importconftest( + conftestpath, + importmode, + rootpath, + consider_namespace_packages=consider_namespace_packages, + ) + clist.append(mod) + self._dirpath2confmods[directory] = clist + + def _getconftestmodules(self, path: pathlib.Path) -> Sequence[types.ModuleType]: + directory = self._get_directory(path) + return self._dirpath2confmods.get(directory, ()) + + def _rget_with_confmod( + self, + name: str, + path: pathlib.Path, + ) -> tuple[types.ModuleType, Any]: + modules = self._getconftestmodules(path) + for mod in reversed(modules): + try: + return mod, getattr(mod, name) + except AttributeError: + continue + raise KeyError(name) + + def _importconftest( + self, + conftestpath: pathlib.Path, + importmode: str | ImportMode, + rootpath: pathlib.Path, + *, + consider_namespace_packages: bool, + ) -> types.ModuleType: + conftestpath_plugin_name = str(conftestpath) + existing = self.get_plugin(conftestpath_plugin_name) + if existing is not None: + return cast(types.ModuleType, existing) + + # conftest.py files there are not in a Python package all have module + # name "conftest", and thus conflict with each other. Clear the existing + # before loading the new one, otherwise the existing one will be + # returned from the module cache. + pkgpath = resolve_package_path(conftestpath) + if pkgpath is None: + try: + del sys.modules[conftestpath.stem] + except KeyError: + pass + + try: + mod = import_path( + conftestpath, + mode=importmode, + root=rootpath, + consider_namespace_packages=consider_namespace_packages, + ) + except Exception as e: + assert e.__traceback__ is not None + raise ConftestImportFailure(conftestpath, cause=e) from e + + self._check_non_top_pytest_plugins(mod, conftestpath) + + self._conftest_plugins.add(mod) + dirpath = conftestpath.parent + if dirpath in self._dirpath2confmods: + for path, mods in self._dirpath2confmods.items(): + if dirpath in path.parents or path == dirpath: + if mod in mods: + raise AssertionError( + f"While trying to load conftest path {conftestpath!s}, " + f"found that the module {mod} is already loaded with path {mod.__file__}. " + "This is not supposed to happen. Please report this issue to pytest." + ) + mods.append(mod) + self.trace(f"loading conftestmodule {mod!r}") + self.consider_conftest(mod, registration_name=conftestpath_plugin_name) + return mod + + def _check_non_top_pytest_plugins( + self, + mod: types.ModuleType, + conftestpath: pathlib.Path, + ) -> None: + if ( + hasattr(mod, "pytest_plugins") + and self._configured + and not self._using_pyargs + ): + msg = ( + "Defining 'pytest_plugins' in a non-top-level conftest is no longer supported:\n" + "It affects the entire test suite instead of just below the conftest as expected.\n" + " {}\n" + "Please move it to a top level conftest file at the rootdir:\n" + " {}\n" + "For more information, visit:\n" + " https://docs.pytest.org/en/stable/deprecations.html#pytest-plugins-in-non-top-level-conftest-files" + ) + fail(msg.format(conftestpath, self._confcutdir), pytrace=False) + + # + # API for bootstrapping plugin loading + # + # + + def consider_preparse( + self, args: Sequence[str], *, exclude_only: bool = False + ) -> None: + """:meta private:""" + i = 0 + n = len(args) + while i < n: + opt = args[i] + i += 1 + if isinstance(opt, str): + if opt == "-p": + try: + parg = args[i] + except IndexError: + return + i += 1 + elif opt.startswith("-p"): + parg = opt[2:] + else: + continue + parg = parg.strip() + if exclude_only and not parg.startswith("no:"): + continue + self.consider_pluginarg(parg) + + def consider_pluginarg(self, arg: str) -> None: + """:meta private:""" + if arg.startswith("no:"): + name = arg[3:] + if name in essential_plugins: + raise UsageError(f"plugin {name} cannot be disabled") + + # PR #4304: remove stepwise if cacheprovider is blocked. + if name == "cacheprovider": + self.set_blocked("stepwise") + self.set_blocked("pytest_stepwise") + + self.set_blocked(name) + if not name.startswith("pytest_"): + self.set_blocked("pytest_" + name) + else: + name = arg + # Unblock the plugin. + self.unblock(name) + if not name.startswith("pytest_"): + self.unblock("pytest_" + name) + self.import_plugin(arg, consider_entry_points=True) + + def consider_conftest( + self, conftestmodule: types.ModuleType, registration_name: str + ) -> None: + """:meta private:""" + self.register(conftestmodule, name=registration_name) + + def consider_env(self) -> None: + """:meta private:""" + self._import_plugin_specs(os.environ.get("PYTEST_PLUGINS")) + + def consider_module(self, mod: types.ModuleType) -> None: + """:meta private:""" + self._import_plugin_specs(getattr(mod, "pytest_plugins", [])) + + def _import_plugin_specs( + self, spec: None | types.ModuleType | str | Sequence[str] + ) -> None: + plugins = _get_plugin_specs_as_list(spec) + for import_spec in plugins: + self.import_plugin(import_spec) + + def import_plugin(self, modname: str, consider_entry_points: bool = False) -> None: + """Import a plugin with ``modname``. + + If ``consider_entry_points`` is True, entry point names are also + considered to find a plugin. + """ + # Most often modname refers to builtin modules, e.g. "pytester", + # "terminal" or "capture". Those plugins are registered under their + # basename for historic purposes but must be imported with the + # _pytest prefix. + assert isinstance(modname, str), ( + f"module name as text required, got {modname!r}" + ) + if self.is_blocked(modname) or self.get_plugin(modname) is not None: + return + + importspec = "_pytest." + modname if modname in builtin_plugins else modname + self.rewrite_hook.mark_rewrite(importspec) + + if consider_entry_points: + loaded = self.load_setuptools_entrypoints("pytest11", name=modname) + if loaded: + return + + try: + __import__(importspec) + except ImportError as e: + raise ImportError( + f'Error importing plugin "{modname}": {e.args[0]}' + ).with_traceback(e.__traceback__) from e + + except Skipped as e: + self.skipped_plugins.append((modname, e.msg or "")) + else: + mod = sys.modules[importspec] + self.register(mod, modname) + + +def _get_plugin_specs_as_list( + specs: None | types.ModuleType | str | Sequence[str], +) -> list[str]: + """Parse a plugins specification into a list of plugin names.""" + # None means empty. + if specs is None: + return [] + # Workaround for #3899 - a submodule which happens to be called "pytest_plugins". + if isinstance(specs, types.ModuleType): + return [] + # Comma-separated list. + if isinstance(specs, str): + return specs.split(",") if specs else [] + # Direct specification. + if isinstance(specs, collections.abc.Sequence): + return list(specs) + raise UsageError( + f"Plugins may be specified as a sequence or a ','-separated string of plugin names. Got: {specs!r}" + ) + + +class Notset: + def __repr__(self): + return "" + + +notset = Notset() + + +def _iter_rewritable_modules(package_files: Iterable[str]) -> Iterator[str]: + """Given an iterable of file names in a source distribution, return the "names" that should + be marked for assertion rewrite. + + For example the package "pytest_mock/__init__.py" should be added as "pytest_mock" in + the assertion rewrite mechanism. + + This function has to deal with dist-info based distributions and egg based distributions + (which are still very much in use for "editable" installs). + + Here are the file names as seen in a dist-info based distribution: + + pytest_mock/__init__.py + pytest_mock/_version.py + pytest_mock/plugin.py + pytest_mock.egg-info/PKG-INFO + + Here are the file names as seen in an egg based distribution: + + src/pytest_mock/__init__.py + src/pytest_mock/_version.py + src/pytest_mock/plugin.py + src/pytest_mock.egg-info/PKG-INFO + LICENSE + setup.py + + We have to take in account those two distribution flavors in order to determine which + names should be considered for assertion rewriting. + + More information: + https://github.com/pytest-dev/pytest-mock/issues/167 + """ + package_files = list(package_files) + seen_some = False + for fn in package_files: + is_simple_module = "/" not in fn and fn.endswith(".py") + is_package = fn.count("/") == 1 and fn.endswith("__init__.py") + if is_simple_module: + module_name, _ = os.path.splitext(fn) + # we ignore "setup.py" at the root of the distribution + # as well as editable installation finder modules made by setuptools + if module_name != "setup" and not module_name.startswith("__editable__"): + seen_some = True + yield module_name + elif is_package: + package_name = os.path.dirname(fn) + seen_some = True + yield package_name + + if not seen_some: + # At this point we did not find any packages or modules suitable for assertion + # rewriting, so we try again by stripping the first path component (to account for + # "src" based source trees for example). + # This approach lets us have the common case continue to be fast, as egg-distributions + # are rarer. + new_package_files = [] + for fn in package_files: + parts = fn.split("/") + new_fn = "/".join(parts[1:]) + if new_fn: + new_package_files.append(new_fn) + if new_package_files: + yield from _iter_rewritable_modules(new_package_files) + + +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. + + :param PytestPluginManager pluginmanager: + A pytest PluginManager. + + :param InvocationParams invocation_params: + Object containing parameters regarding the :func:`pytest.main` + invocation. + """ + + @final + @dataclasses.dataclass(frozen=True) + class InvocationParams: + """Holds parameters passed during :func:`pytest.main`. + + The object attributes are read-only. + + .. versionadded:: 5.1 + + .. note:: + + Note that the environment variable ``PYTEST_ADDOPTS`` and the ``addopts`` + configuration option are handled by pytest, not being included in the ``args`` attribute. + + Plugins accessing ``InvocationParams`` must be aware of that. + """ + + args: tuple[str, ...] + """The command-line arguments as passed to :func:`pytest.main`.""" + plugins: Sequence[str | _PluggyPlugin] | None + """Extra plugins, might be `None`.""" + dir: pathlib.Path + """The directory from which :func:`pytest.main` was invoked.""" + + def __init__( + self, + *, + args: Iterable[str], + plugins: Sequence[str | _PluggyPlugin] | None, + dir: pathlib.Path, + ) -> None: + object.__setattr__(self, "args", tuple(args)) + object.__setattr__(self, "plugins", plugins) + object.__setattr__(self, "dir", dir) + + class ArgsSource(enum.Enum): + """Indicates the source of the test arguments. + + .. versionadded:: 7.2 + """ + + #: Command line arguments. + ARGS = enum.auto() + #: Invocation directory. + INVOCATION_DIR = enum.auto() + INCOVATION_DIR = INVOCATION_DIR # backwards compatibility alias + #: 'testpaths' configuration value. + TESTPATHS = enum.auto() + + # Set by cacheprovider plugin. + cache: Cache + + def __init__( + self, + pluginmanager: PytestPluginManager, + *, + invocation_params: InvocationParams | None = None, + ) -> None: + if invocation_params is None: + invocation_params = self.InvocationParams( + args=(), plugins=None, dir=pathlib.Path.cwd() + ) + + self.option = argparse.Namespace() + """Access to command line option as attributes. + + :type: argparse.Namespace + """ + + self.invocation_params = invocation_params + """The parameters with which pytest was invoked. + + :type: InvocationParams + """ + + self._parser = Parser( + usage=f"%(prog)s [options] [{FILE_OR_DIR}] [{FILE_OR_DIR}] [...]", + processopt=self._processopt, + _ispytest=True, + ) + self.pluginmanager = pluginmanager + """The plugin manager handles plugin registration and hook invocation. + + :type: PytestPluginManager + """ + + self.stash = Stash() + """A place where plugins can store information on the config for their + own use. + + :type: Stash + """ + # Deprecated alias. Was never public. Can be removed in a few releases. + self._store = self.stash + + self.trace = self.pluginmanager.trace.root.get("config") + self.hook: pluggy.HookRelay = PathAwareHookProxy(self.pluginmanager.hook) # type: ignore[assignment] + self._inicache: dict[str, Any] = {} + self._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] = [] + + @property + def inicfg(self) -> _DeprecatedInicfgProxy: + return _DeprecatedInicfgProxy(self) + + @property + def rootpath(self) -> pathlib.Path: + """The path to the :ref:`rootdir `. + + .. versionadded:: 6.1 + """ + return self._rootpath + + @property + def inipath(self) -> pathlib.Path | None: + """The path to the :ref:`configfile `. + + .. versionadded:: 6.1 + """ + return self._inipath + + def add_cleanup(self, func: Callable[[], None]) -> None: + """Add a function to be called when the config object gets out of + use (usually coinciding with pytest_unconfigure). + """ + self._cleanup_stack.callback(func) + + def _do_configure(self) -> None: + assert not self._configured + self._configured = True + self.hook.pytest_configure.call_historic(kwargs=dict(config=self)) + + def _ensure_unconfigure(self) -> None: + 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: TerminalReporter | None = self.pluginmanager.get_plugin( + "terminalreporter" + ) + assert terminalreporter is not None + return terminalreporter._tw + + def pytest_cmdline_parse( + self, pluginmanager: PytestPluginManager, args: list[str] + ) -> Config: + try: + self.parse(args) + except UsageError: + # Handle `--version --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 show_version_verbose + + # 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.optparser.print_help() + sys.stdout.write( + "\nNOTE: displaying only minimal help due to UsageError.\n\n" + ) + + raise + + return self + + def notify_exception( + self, + excinfo: ExceptionInfo[BaseException], + option: argparse.Namespace | None = None, + ) -> None: + if option and getattr(option, "fulltrace", False): + style: TracebackStyle = "long" + else: + style = "native" + excrepr = excinfo.getrepr( + funcargs=True, showlocals=getattr(option, "showlocals", False), style=style + ) + res = self.hook.pytest_internalerror(excrepr=excrepr, excinfo=excinfo) + if not any(res): + for line in str(excrepr).split("\n"): + sys.stderr.write(f"INTERNALERROR> {line}\n") + sys.stderr.flush() + + def cwd_relative_nodeid(self, nodeid: str) -> str: + # nodeid's are relative to the rootpath, compute relative to cwd. + if self.invocation_params.dir != self.rootpath: + base_path_part, *nodeid_part = nodeid.split("::") + # Only process path part + fullpath = self.rootpath / base_path_part + relative_path = bestrelpath(self.invocation_params.dir, fullpath) + + nodeid = "::".join([relative_path, *nodeid_part]) + return nodeid + + @classmethod + def fromdictargs(cls, option_dict: Mapping[str, Any], args: list[str]) -> Config: + """Constructor usable for subprocesses.""" + config = get_config(args) + config.option.__dict__.update(option_dict) + config.parse(args, addopts=False) + for x in config.option.plugins: + config.pluginmanager.consider_pluginarg(x) + return config + + def _processopt(self, opt: Argument) -> None: + for name in opt._short_opts + opt._long_opts: + self._opt2dest[name] = opt.dest + + if hasattr(opt, "default"): + if not hasattr(self.option, opt.dest): + setattr(self.option, opt.dest, opt.default) + + @hookimpl(trylast=True) + def pytest_load_initial_conftests(self, early_config: Config) -> None: + # We haven't fully parsed the command line arguments yet, so + # early_config.args it not set yet. But we need it for + # discovering the initial conftests. So "pre-run" the logic here. + # It will be done for real in `parse()`. + args, _args_source = early_config._decide_args( + args=early_config.known_args_namespace.file_or_dir, + pyargs=early_config.known_args_namespace.pyargs, + testpaths=early_config.getini("testpaths"), + invocation_dir=early_config.invocation_params.dir, + rootpath=early_config.rootpath, + warn=False, + ) + self.pluginmanager._set_initial_conftests( + args=args, + pyargs=early_config.known_args_namespace.pyargs, + noconftest=early_config.known_args_namespace.noconftest, + rootpath=early_config.rootpath, + confcutdir=early_config.known_args_namespace.confcutdir, + invocation_dir=early_config.invocation_params.dir, + importmode=early_config.known_args_namespace.importmode, + consider_namespace_packages=early_config.getini( + "consider_namespace_packages" + ), + ) + + def _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. + """ + 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 + + try: + hook = _pytest.assertion.install_importhook(self) + except SystemError: + mode = "plain" + else: + self._mark_plugins_for_rewrite(hook, disable_autoload) + self._warn_about_missing_assertion(mode) + + 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 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() + if any(ep.group == "pytest11" for ep in dist.entry_points) + for file in dist.files or [] + ) + + for name in _iter_rewritable_modules(package_files): + hook.mark_rewrite(name) + + def _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.extra_info["config source"] = via + try: + self._parser.parse_known_and_unknown_args( + args, namespace=copy.copy(self.option) + ) + finally: + self._parser.extra_info.pop("config source", None) + + return args + + def _decide_args( + self, + *, + args: list[str], + pyargs: bool, + testpaths: list[str], + invocation_dir: pathlib.Path, + rootpath: pathlib.Path, + warn: bool, + ) -> tuple[list[str], ArgsSource]: + """Decide the args (initial paths/nodeids) to use given the relevant inputs. + + :param warn: Whether can issue warnings. + + :returns: The args and the args source. Guaranteed to be non-empty. + """ + if args: + source = Config.ArgsSource.ARGS + result = args + else: + if invocation_dir == rootpath: + source = Config.ArgsSource.TESTPATHS + if pyargs: + result = testpaths + else: + result = [] + for path in testpaths: + result.extend(sorted(glob.iglob(path, recursive=True))) + if testpaths and not result: + if warn: + warning_text = ( + "No files were found in testpaths; " + "consider removing or adjusting your testpaths configuration. " + "Searching recursively from the current directory instead." + ) + self.issue_config_time_warning( + PytestConfigWarning(warning_text), stacklevel=3 + ) + else: + result = [] + if not result: + source = Config.ArgsSource.INVOCATION_DIR + result = [str(invocation_dir)] + return result, source + + @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: + return (yield) + finally: + self._validate_config_options() + + def _checkversion(self) -> None: + import pytest + + 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( + f"{self.inipath}: 'minversion' must be a single value" + ) + + if Version(minver) > Version(pytest.__version__): + raise pytest.UsageError( + f"{self.inipath}: 'minversion' requires pytest-{minver}, actual pytest-{pytest.__version__}'" + ) + + def _validate_config_options(self) -> None: + for key in sorted(self._get_unknown_ini_keys()): + self._warn_or_fail_if_strict(f"Unknown config option: {key}\n") + + def _validate_plugins(self) -> None: + required_plugins = sorted(self.getini("required_plugins")) + if not required_plugins: + return + + # Imported lazily to improve start-up time. + from packaging.requirements import InvalidRequirement + from packaging.requirements import Requirement + from packaging.version import Version + + plugin_info = self.pluginmanager.list_plugin_distinfo() + plugin_dist_info = {dist.project_name: dist.version for _, dist in plugin_info} + + missing_plugins = [] + for required_plugin in required_plugins: + try: + req = Requirement(required_plugin) + except InvalidRequirement: + missing_plugins.append(required_plugin) + continue + + if req.name not in plugin_dist_info: + missing_plugins.append(required_plugin) + elif not req.specifier.contains( + Version(plugin_dist_info[req.name]), prereleases=True + ): + missing_plugins.append(required_plugin) + + if missing_plugins: + raise UsageError( + "Missing required plugins: {}".format(", ".join(missing_plugins)), + ) + + def _warn_or_fail_if_strict(self, message: str) -> None: + 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) -> 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: + # Parse given cmdline arguments into this config object. + assert self.args == [], ( + "can only parse cmdline args at most once per Config object" + ) + + self.hook.pytest_addhooks.call_historic( + kwargs=dict(pluginmanager=self.pluginmanager) + ) + + 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: + 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: + 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 hook wrappers around ``pytest_configure``. + + This function is mainly intended for plugins that need to issue warnings during + ``pytest_configure`` (or similar stages). + + :param warning: The warning instance. + :param stacklevel: stacklevel forwarded to warnings.warn. + """ + if self.pluginmanager.is_blocked("warnings"): + return + + cmdline_filters = self.known_args_namespace.pythonwarnings or [] + config_filters = self.getini("filterwarnings") + + with warnings.catch_warnings(record=True) as records: + warnings.simplefilter("always", type(warning)) + apply_warning_filters(config_filters, cmdline_filters) + warnings.warn(warning, stacklevel=stacklevel) + + if records: + frame = sys._getframe(stacklevel - 1) + location = frame.f_code.co_filename, frame.f_lineno, frame.f_code.co_name + self.hook.pytest_warning_recorded.call_historic( + kwargs=dict( + warning_message=records[0], + when="config", + nodeid="", + location=location, + ) + ) + + def addinivalue_line(self, name: str, line: str) -> None: + """Add a line to 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) -> 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[canonical_name] + except KeyError: + 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: 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[canonical_name] + except KeyError as e: + raise ValueError(f"unknown configuration value: {name!r}") from e + + # 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: + 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 (ini mode): + # a_line_list = ["tests", "acceptance"] + # + # in this case, we already have a list ready to use. + if type == "paths": + dp = ( + self.inipath.parent + if self.inipath is not None + else self.invocation_params.dir + ) + input_values = shlex.split(value) if isinstance(value, str) else value + return [dp / x for x in input_values] + elif type == "args": + return shlex.split(value) if isinstance(value, str) else value + elif type == "linelist": + if isinstance(value, str): + return [t for t in map(lambda x: x.strip(), value.split("\n")) if t] + else: + return value + elif type == "bool": + return _strtobool(str(value).strip()) + elif type == "string": + return value + elif type == "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: pathlib.Path + ) -> list[pathlib.Path] | None: + try: + mod, relroots = self.pluginmanager._rget_with_confmod(name, path) + except KeyError: + return None + assert mod.__file__ is not None + modpath = pathlib.Path(mod.__file__).parent + values: list[pathlib.Path] = [] + for relroot in relroots: + if isinstance(relroot, os.PathLike): + relroot = pathlib.Path(relroot) + else: + relroot = relroot.replace("/", os.sep) + relroot = absolutepath(modpath / relroot) + values.append(relroot) + return values + + def 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 + the literal ``--OPT`` option instead of the "dest" option name. + :param default: Fallback value if no option of that name is **declared** via :hook:`pytest_addoption`. + Note this parameter will be ignored when the option is **declared** even if the option's value is ``None``. + :param skip: If ``True``, raise :func:`pytest.skip` if option is undeclared or has a ``None`` value. + Note that even if ``True``, if a default was specified it will be returned instead of a skip. + """ + name = self._opt2dest.get(name, name) + try: + val = getattr(self.option, name) + if val is None and skip: + raise AttributeError(name) + return val + except AttributeError as e: + if default is not notset: + return default + if skip: + import pytest + + pytest.skip(f"no {name!r} option found") + raise ValueError(f"no option named {name!r}") from e + + def getvalue(self, name: str, path=None): + """Deprecated, use getoption() instead.""" + return self.getoption(name) + + def getvalueorskip(self, name: str, path=None): + """Deprecated, use getoption(skip=True) instead.""" + return self.getoption(name, skip=True) + + #: Verbosity type for failed assertions (see :confval:`verbosity_assertions`). + VERBOSITY_ASSERTIONS: Final = "assertions" + #: Verbosity type for test case execution (see :confval:`verbosity_test_cases`). + VERBOSITY_TEST_CASES: Final = "test_cases" + #: Verbosity 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": + warning_text = ( + "ASSERTIONS ARE NOT EXECUTED" + " and FAILING TESTS WILL PASS. Are you" + " using python -O?" + ) + else: + warning_text = ( + "assertions not in test modules or" + " plugins will be ignored" + " because assert statements are not executed " + "by the underlying Python interpreter " + "(are you using python -O?)\n" + ) + self.issue_config_time_warning( + PytestConfigWarning(warning_text), + stacklevel=3, + ) + + def _warn_about_skipped_plugins(self) -> None: + for module_name, msg in self.pluginmanager.skipped_plugins: + self.issue_config_time_warning( + PytestConfigWarning(f"skipped plugin {module_name!r}: {msg}"), + stacklevel=2, + ) + + +def _assertion_supported() -> bool: + try: + assert False + except AssertionError: + return True + else: + return False # type: ignore[unreachable] + + +def create_terminal_writer( + config: Config, file: TextIO | None = None +) -> TerminalWriter: + """Create a TerminalWriter instance configured according to the options + in the config object. + + Every code which requires a TerminalWriter object and has access to a + config object should use this function. + """ + tw = TerminalWriter(file=file) + + if config.option.color == "yes": + tw.hasmarkup = True + elif config.option.color == "no": + tw.hasmarkup = False + + if config.option.code_highlight == "yes": + tw.code_highlight = True + elif config.option.code_highlight == "no": + tw.code_highlight = False + + return tw + + +def _strtobool(val: str) -> bool: + """Convert a string representation of truth to True or False. + + True values are 'y', 'yes', 't', 'true', 'on', and '1'; false values + are 'n', 'no', 'f', 'false', 'off', and '0'. Raises ValueError if + 'val' is anything else. + + .. note:: Copied from distutils.util. + """ + val = val.lower() + if val in ("y", "yes", "t", "true", "on", "1"): + return True + elif val in ("n", "no", "f", "false", "off", "0"): + return False + else: + raise ValueError(f"invalid truth value {val!r}") + + +@lru_cache(maxsize=50) +def parse_warning_filter( + arg: str, *, escape: bool +) -> tuple[warnings._ActionKind, str, type[Warning], str, int]: + """Parse a warnings filter string. + + This is copied from warnings._setoption with the following changes: + + * Does not apply the filter. + * Escaping is optional. + * Raises UsageError so we get nice error messages on failure. + """ + __tracebackhide__ = True + error_template = dedent( + f"""\ + while parsing the following warning configuration: + + {arg} + + This error occurred: + + {{error}} + """ + ) + + parts = arg.split(":") + if len(parts) > 5: + doc_url = ( + "https://docs.python.org/3/library/warnings.html#describing-warning-filters" + ) + error = dedent( + f"""\ + Too many fields ({len(parts)}), expected at most 5 separated by colons: + + action:message:category:module:line + + For more information please consult: {doc_url} + """ + ) + raise UsageError(error_template.format(error=error)) + + while len(parts) < 5: + parts.append("") + action_, message, category_, module, lineno_ = (s.strip() for s in parts) + try: + action: warnings._ActionKind = warnings._getaction(action_) # type: ignore[attr-defined] + except warnings._OptionError as e: + raise UsageError(error_template.format(error=str(e))) from None + try: + category: type[Warning] = _resolve_warning_category(category_) + except ImportError: + raise + except Exception: + exc_info = ExceptionInfo.from_current() + exception_text = exc_info.getrepr(style="native") + raise UsageError(error_template.format(error=exception_text)) from None + if message and escape: + message = re.escape(message) + if module and escape: + module = re.escape(module) + r"\Z" + if lineno_: + try: + lineno = int(lineno_) + if lineno < 0: + raise ValueError("number is negative") + except ValueError as e: + raise UsageError( + error_template.format(error=f"invalid lineno {lineno_!r}: {e}") + ) from None + else: + lineno = 0 + 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]: + """ + Copied from warnings._getcategory, but changed so it lets exceptions (specially ImportErrors) + propagate so we can get access to their tracebacks (#9218). + """ + __tracebackhide__ = True + if not category: + return Warning + + if "." not in category: + import builtins as m + + klass = category + else: + module, _, klass = category.rpartition(".") + m = __import__(module, None, None, [klass]) + cat = getattr(m, klass) + if not issubclass(cat, Warning): + raise UsageError(f"{cat} is not a Warning subclass") + return cast(type[Warning], cat) + + +def apply_warning_filters( + config_filters: Iterable[str], cmdline_filters: Iterable[str] +) -> None: + """Applies pytest-configured filters to the warnings module""" + # Filters should have this precedence: cmdline options, config. + # Filters should be applied in the inverse order of precedence. + for arg in config_filters: + 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: + 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/py311/lib/python3.11/site-packages/_pytest/config/argparsing.py b/py311/lib/python3.11/site-packages/_pytest/config/argparsing.py new file mode 100644 index 0000000000000000000000000000000000000000..8216ad8b226a5fc086d68a0022413036dbb64b9a --- /dev/null +++ b/py311/lib/python3.11/site-packages/_pytest/config/argparsing.py @@ -0,0 +1,578 @@ +# 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 +from typing import Any +from typing import final +from typing import Literal +from typing import NoReturn + +from .exceptions import UsageError +import _pytest._io +from _pytest.deprecated import check_ispytest + + +FILE_OR_DIR = "file_or_dir" + + +class NotSet: + def __repr__(self) -> str: + return "" + + +NOT_SET = NotSet() + + +@final +class Parser: + """Parser for command line arguments and config-file values. + + :ivar extra_info: Dict of generic param -> value to display in case + there's an error processing the command line arguments. + """ + + def __init__( + self, + usage: str | None = None, + processopt: Callable[[Argument], None] | None = None, + *, + _ispytest: bool = False, + ) -> None: + check_ispytest(_ispytest) + + 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: str | None = None + ) -> OptionGroup: + """Get (or create) a named option Group. + + :param name: Name of the option group. + :param description: Long description for --help output. + :param after: Name of another group, used for ordering --help output. + :returns: The option group. + + The returned group object has an ``addoption`` method with the same + signature as :func:`parser.addoption ` but + will be shown in the respective group in the output of + ``pytest --help``. + """ + for group in self._groups: + if group.name == name: + return group + + 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: + """Register a command line option. + + :param opts: + Option names, can be short or long options. + :param attrs: + Same attributes as the argparse library's :meth:`add_argument() + ` function accepts. + + After command line parsing, options are available on the pytest config + object via ``config.option.NAME`` where ``NAME`` is usually set + by passing a ``dest`` attribute, for example + ``addoption("--long", dest="NAME", ...)``. + """ + self._anonymous.addoption(*opts, **attrs) + + def parse( + self, + args: Sequence[str | os.PathLike[str]], + namespace: argparse.Namespace | None = None, + ) -> argparse.Namespace: + """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 + + try_argcomplete(self.optparser) + strargs = [os.fspath(x) for x in args] + 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[str | os.PathLike[str]], + namespace: argparse.Namespace | None = None, + ) -> argparse.Namespace: + """Parse the known arguments at this point. + + :returns: An argparse namespace object. + """ + return self.parse_known_and_unknown_args(args, namespace=namespace)[0] + + def parse_known_and_unknown_args( + self, + args: Sequence[str | os.PathLike[str]], + namespace: argparse.Namespace | None = None, + ) -> tuple[argparse.Namespace, list[str]]: + """Parse the known arguments at this point, and also return the + remaining unknown flag arguments. + + :returns: + A tuple containing an argparse namespace object for the known + arguments, and a list of unknown flag arguments. + """ + strargs = [os.fspath(x) for x in args] + 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: Literal[ + "string", "paths", "pathlist", "args", "linelist", "bool", "int", "float" + ] + | None = None, + default: Any = NOT_SET, + *, + aliases: Sequence[str] = (), + ) -> None: + """Register a configuration file option. + + :param name: + Name of the configuration. + :param type: + Type of the configuration. Can be: + + * ``string``: a string + * ``bool``: a boolean + * ``args``: a list of strings, separated as in a shell + * ``linelist``: a list of strings, separated by line breaks + * ``paths``: a list of :class:`pathlib.Path`, separated as in a shell + * ``pathlist``: a list of ``py.path``, separated as in a shell + * ``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 config-file option exists but is queried. + :param aliases: + Additional names by which this option can be referenced. + Aliases resolve to the canonical name. + + .. 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", + "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) + + 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: Argument | str) -> None: + self.msg = msg + self.option_id = str(option) + + def __str__(self) -> str: + if self.option_id: + return f"option {self.option_id}: {self.msg}" + else: + return self.msg + + +class Argument: + """Class that mimics the necessary behaviour of optparse.Option. + + It's currently a least effort implementation and ignoring choices + and integer prefixes. + + https://docs.python.org/3/library/optparse.html#optparse-standard-option-types + """ + + def __init__(self, *names: str, **attrs: Any) -> None: + """Store params in private vars for use in add_argument.""" + self._attrs = attrs + self._short_opts: list[str] = [] + self._long_opts: list[str] = [] + try: + self.type = attrs["type"] + except KeyError: + pass + try: + # Attribute existence is tested in Config._processopt. + self.default = attrs["default"] + except KeyError: + pass + self._set_opt_strings(names) + dest: str | None = attrs.get("dest") + if dest: + self.dest = dest + elif self._long_opts: + self.dest = self._long_opts[0][2:].replace("-", "_") + else: + try: + self.dest = self._short_opts[0][1:] + except IndexError as e: + self.dest = "???" # Needed for the error repr. + raise ArgumentError("need a long or short option", self) from e + + def names(self) -> list[str]: + return self._short_opts + self._long_opts + + def attrs(self) -> Mapping[str, Any]: + # Update any attributes set by processopt. + for attr in ("default", "dest", "help", self.dest): + try: + self._attrs[attr] = getattr(self, attr) + except AttributeError: + pass + return self._attrs + + def _set_opt_strings(self, opts: Sequence[str]) -> None: + """Directly from optparse. + + Might not be necessary as this is passed to argparse later on. + """ + for opt in opts: + if len(opt) < 2: + raise ArgumentError( + f"invalid option string {opt!r}: " + "must be at least two characters long", + self, + ) + elif len(opt) == 2: + if not (opt[0] == "-" and opt[1] != "-"): + raise ArgumentError( + f"invalid short option string {opt!r}: " + "must be of the form -x, (x any non-dash char)", + self, + ) + self._short_opts.append(opt) + else: + if not (opt[0:2] == "--" and opt[2] != "-"): + raise ArgumentError( + f"invalid long option string {opt!r}: " + "must start with --, followed by non-dash", + self, + ) + self._long_opts.append(opt) + + def __repr__(self) -> str: + args: list[str] = [] + if self._short_opts: + args += ["_short_opts: " + repr(self._short_opts)] + if self._long_opts: + args += ["_long_opts: " + repr(self._long_opts)] + args += ["dest: " + repr(self.dest)] + if hasattr(self, "type"): + args += ["type: " + repr(self.type)] + if hasattr(self, "default"): + args += ["default: " + repr(self.default)] + return "Argument({})".format(", ".join(args)) + + +class OptionGroup: + """A group of options shown in its own section.""" + + def __init__( + self, + arggroup: argparse._ArgumentGroup, + name: str, + parser: Parser | None, + _ispytest: bool = False, + ) -> None: + check_ispytest(_ispytest) + self._arggroup = arggroup + self.name = name + self.options: list[Argument] = [] + self.parser = parser + + def addoption(self, *opts: str, **attrs: Any) -> None: + """Add an option to this group. + + If a shortened version of a long option is specified, it will + be suppressed in the help. ``addoption('--twowords', '--two-words')`` + results in help showing ``--two-words`` only, but ``--twowords`` gets + accepted **and** the automatic destination is in ``args.twowords``. + + :param opts: + Option names, can be short or long options. + :param attrs: + Same attributes as the argparse library's :meth:`add_argument() + ` function accepts. + """ + conflict = set(opts).intersection( + name for opt in self.options for name in opt.names() + ) + if conflict: + raise ValueError(f"option names {conflict} already added") + option = Argument(*opts, **attrs) + self._addoption_instance(option, shortupper=False) + + def _addoption(self, *opts: str, **attrs: Any) -> None: + option = Argument(*opts, **attrs) + self._addoption_instance(option, shortupper=True) + + def _addoption_instance(self, option: Argument, shortupper: bool = False) -> None: + if not shortupper: + for opt in option._short_opts: + if opt[0] == "-" and opt[1].islower(): + raise ValueError("lowercase shortoptions reserved") + + if self.parser: + self.parser.processoption(option) + + self._arggroup.add_argument(*option.names(), **option.attrs()) + self.options.append(option) + + +class PytestArgumentParser(argparse.ArgumentParser): + def __init__( + self, + parser: Parser, + usage: str | None, + extra_info: dict[str, str], + ) -> None: + self._parser = parser + super().__init__( + 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 + + def error(self, message: str) -> NoReturn: + """Transform argparse error message into UsageError.""" + msg = f"{self.prog}: error: {message}" + 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) + + +class DropShorterLongHelpFormatter(argparse.HelpFormatter): + """Shorten help for long options that differ only in extra hyphens. + + - Collapse **long** options that are the same except for extra hyphens. + - Shortcut if there are only two options and one of them is a short one. + - Cache result on the action object as this is called at least 2 times. + """ + + def __init__(self, *args: Any, **kwargs: Any) -> None: + # Use more accurate terminal width. + if "width" not in kwargs: + kwargs["width"] = _pytest._io.get_terminal_width() + super().__init__(*args, **kwargs) + + def _format_action_invocation(self, action: argparse.Action) -> str: + orgstr = super()._format_action_invocation(action) + if orgstr and orgstr[0] != "-": # only optional arguments + return orgstr + res: str | None = getattr(action, "_formatted_action_invocation", None) + if res: + return res + options = orgstr.split(", ") + if len(options) == 2 and (len(options[0]) == 2 or len(options[1]) == 2): + # a shortcut for '-h, --help' or '--abc', '-a' + action._formatted_action_invocation = orgstr # type: ignore + return orgstr + return_list = [] + short_long: dict[str, str] = {} + for option in options: + if len(option) == 2 or option[2] == " ": + continue + if not option.startswith("--"): + raise ArgumentError( + f'long optional argument without "--": [{option}]', option + ) + xxoption = option[2:] + shortened = xxoption.replace("-", "") + if shortened not in short_long or len(short_long[shortened]) < len( + xxoption + ): + short_long[shortened] = xxoption + # now short_long has been filled out to the longest with dashes + # **and** we keep the right option ordering from add_argument + for option in options: + if len(option) == 2 or option[2] == " ": + return_list.append(option) + if option[2:] == short_long.get(option.replace("-", "")): + return_list.append(option.replace(" ", "=", 1)) + formatted_action_invocation = ", ".join(return_list) + action._formatted_action_invocation = formatted_action_invocation # type: ignore + return formatted_action_invocation + + def _split_lines(self, text, width): + """Wrap lines after splitting on original newlines. + + This allows to have explicit line breaks in the help text. + """ + import textwrap + + lines = [] + for line in text.splitlines(): + lines.extend(textwrap.wrap(line.strip(), width)) + return lines + + +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/py311/lib/python3.11/site-packages/_pytest/config/compat.py b/py311/lib/python3.11/site-packages/_pytest/config/compat.py new file mode 100644 index 0000000000000000000000000000000000000000..21eab4c7e47aa2af1690887716055943c0f99fdc --- /dev/null +++ b/py311/lib/python3.11/site-packages/_pytest/config/compat.py @@ -0,0 +1,85 @@ +from __future__ import annotations + +from collections.abc import Mapping +import functools +from pathlib import Path +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 + + +# hookname: (Path, LEGACY_PATH) +imply_paths_hooks: Mapping[str, tuple[str, str]] = { + "pytest_ignore_collect": ("collection_path", "path"), + "pytest_collect_file": ("file_path", "path"), + "pytest_pycollect_makemodule": ("module_path", "path"), + "pytest_report_header": ("start_path", "startdir"), + "pytest_report_collectionfinish": ("start_path", "startdir"), +} + + +def _check_path(path: Path, fspath: LEGACY_PATH) -> None: + if Path(fspath) != path: + raise ValueError( + f"Path({fspath!r}) != {path!r}\n" + "if both path and fspath are given they need to be equal" + ) + + +class PathAwareHookProxy: + """ + this helper wraps around hook callers + until pluggy supports fixingcalls, this one will do + + it currently doesn't return full hook caller proxies for fixed hooks, + this may have to be changed later depending on bugs + """ + + def __init__(self, hook_relay: pluggy.HookRelay) -> None: + self._hook_relay = hook_relay + + def __dir__(self) -> list[str]: + return dir(self._hook_relay) + + def __getattr__(self, key: str) -> pluggy.HookCaller: + hook: pluggy.HookCaller = getattr(self._hook_relay, key) + if key not in imply_paths_hooks: + self.__dict__[key] = hook + return hook + else: + path_var, fspath_var = imply_paths_hooks[key] + + @functools.wraps(hook) + def fixed_hook(**kw: Any) -> Any: + path_value: Path | None = kw.pop(path_var, None) + fspath_value: LEGACY_PATH | None = kw.pop(fspath_var, None) + if fspath_value is not None: + warnings.warn( + HOOK_LEGACY_PATH_ARG.format( + pylib_path_arg=fspath_var, pathlib_path_arg=path_var + ), + stacklevel=2, + ) + if path_value is not None: + if fspath_value is not None: + _check_path(path_value, fspath_value) + else: + fspath_value = legacy_path(path_value) + else: + assert fspath_value is not None + path_value = Path(fspath_value) + + kw[path_var] = path_value + kw[fspath_var] = fspath_value + return hook(**kw) + + fixed_hook.name = hook.name # type: ignore[attr-defined] + fixed_hook.spec = hook.spec # type: ignore[attr-defined] + fixed_hook.__name__ = key + self.__dict__[key] = fixed_hook + return fixed_hook # type: ignore[return-value] diff --git a/py311/lib/python3.11/site-packages/_pytest/config/exceptions.py b/py311/lib/python3.11/site-packages/_pytest/config/exceptions.py new file mode 100644 index 0000000000000000000000000000000000000000..d84a9ea67e07f3d9591883360fb5ee0c92422787 --- /dev/null +++ b/py311/lib/python3.11/site-packages/_pytest/config/exceptions.py @@ -0,0 +1,15 @@ +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 + argument parsing and validation.""" diff --git a/py311/lib/python3.11/site-packages/_pytest/config/findpaths.py b/py311/lib/python3.11/site-packages/_pytest/config/findpaths.py new file mode 100644 index 0000000000000000000000000000000000000000..3c628a09c2da7c16db785b4add163a05893ecad0 --- /dev/null +++ b/py311/lib/python3.11/site-packages/_pytest/config/findpaths.py @@ -0,0 +1,350 @@ +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 +from pathlib import Path +import sys +from typing import Literal +from typing import TypeAlias + +import iniconfig + +from .exceptions import UsageError +from _pytest.outcomes import fail +from _pytest.pathlib import absolutepath +from _pytest.pathlib import commonpath +from _pytest.pathlib import safe_exists + + +@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: + """Parse the given generic '.ini' file using legacy IniConfig parser, returning + the parsed object. + + Raise UsageError if the file cannot be parsed. + """ + try: + return iniconfig.IniConfig(str(path)) + except iniconfig.ParseError as exc: + raise UsageError(str(exc)) from exc + + +def load_config_dict_from_file( + filepath: Path, +) -> 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 { + 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 in {"pytest.ini", ".pytest.ini"}: + return {} + + # '.cfg' files are considered if they contain a "[tool:pytest]" section. + elif filepath.suffix == ".cfg": + iniconfig = _parse_ini_config(filepath) + + if "tool:pytest" in iniconfig.sections: + return { + 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] 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 + else: + import tomli as tomllib + + toml_text = filepath.read_text(encoding="utf-8") + try: + config = tomllib.loads(toml_text) + except tomllib.TOMLDecodeError as exc: + raise UsageError(f"{filepath}: {exc}") from exc + + # 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 {} + + # 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[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, 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", + "tox.ini", + "setup.cfg", + ] + args = [x for x in args if not str(x).startswith("-")] + if not args: + args = [invocation_dir] + found_pyproject_toml: Path | None = None + 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: + 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( + invocation_dir: Path, + paths: Iterable[Path], +) -> Path: + common_ancestor: Path | None = None + for path in paths: + if not path.exists(): + continue + if common_ancestor is None: + common_ancestor = path + else: + if common_ancestor in path.parents or path == common_ancestor: + continue + elif path in common_ancestor.parents: + common_ancestor = path + else: + shared = commonpath(path, common_ancestor) + if shared is not None: + common_ancestor = shared + if common_ancestor is None: + common_ancestor = invocation_dir + elif common_ancestor.is_file(): + common_ancestor = common_ancestor.parent + return common_ancestor + + +def get_dirs_from_args(args: Iterable[str]) -> list[Path]: + def is_option(x: str) -> bool: + return x.startswith("-") + + def get_file_part_from_node_id(x: str) -> str: + return x.split("::")[0] + + def get_dir_from_path(path: Path) -> Path: + if path.is_dir(): + return path + return path.parent + + # These look like paths but may not exist + possible_paths = ( + absolutepath(get_file_part_from_node_id(arg)) + for arg in args + if not is_option(arg) + ) + + return [get_dir_from_path(path) for path in possible_paths if safe_exists(path)] + + +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: str | None, + override_ini: Sequence[str] | None, + args: Sequence[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: Path | None = inipath_ + inicfg = load_config_dict_from_file(inipath_) or {} + if rootdir_cmd_arg is None: + rootdir = inipath_.parent + else: + ancestor = get_common_ancestor(invocation_dir, dirs) + rootdir, inipath, inicfg, 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(): + rootdir = possible_rootdir + break + else: + if dirs != [ancestor]: + rootdir, inipath, inicfg, _ = locate_config(invocation_dir, dirs) + if rootdir is None: + rootdir = get_common_ancestor( + invocation_dir, [invocation_dir, ancestor] + ) + if is_fs_root(rootdir): + rootdir = ancestor + if rootdir_cmd_arg: + rootdir = absolutepath(os.path.expandvars(rootdir_cmd_arg)) + if not rootdir.is_dir(): + raise UsageError( + f"Directory '{rootdir}' not found. Check your '--rootdir' option." + ) + + ini_overrides = parse_override_ini(override_ini) + inicfg.update(ini_overrides) + + assert rootdir is not None + return rootdir, inipath, inicfg, ignored_config_files + + +def is_fs_root(p: Path) -> bool: + r""" + Return True if the given path is pointing to the root of the + file system ("/" on Unix and "C:\\" on Windows for example). + """ + return os.path.splitdrive(str(p))[1] == os.sep diff --git a/py311/lib/python3.11/site-packages/_pytest/mark/__init__.py b/py311/lib/python3.11/site-packages/_pytest/mark/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..841d7811fddbb96a7ac7615e998602b834ba091a --- /dev/null +++ b/py311/lib/python3.11/site-packages/_pytest/mark/__init__.py @@ -0,0 +1,301 @@ +"""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 TYPE_CHECKING + +from .expression import Expression +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 +from .structures import MarkGenerator +from .structures import ParameterSet +from _pytest.config import Config +from _pytest.config import ExitCode +from _pytest.config import hookimpl +from _pytest.config import UsageError +from _pytest.config.argparsing import NOT_SET +from _pytest.config.argparsing import Parser +from _pytest.stash import StashKey + + +if TYPE_CHECKING: + from _pytest.nodes import Item + + +__all__ = [ + "HIDDEN_PARAM", + "MARK_GEN", + "Mark", + "MarkDecorator", + "MarkGenerator", + "ParameterSet", + "get_empty_parameterset_mark", +] + + +old_mark_config_key = StashKey[Config | None]() + + +def param( + *values: object, + marks: MarkDecorator | Collection[MarkDecorator | Mark] = (), + id: str | _HiddenParam | None = None, +) -> ParameterSet: + """Specify a parameter in `pytest.mark.parametrize`_ calls or + :ref:`parametrized fixtures `. + + .. code-block:: python + + @pytest.mark.parametrize( + "test_input,expected", + [ + ("3+5", 8), + pytest.param("6*9", 42, marks=pytest.mark.xfail), + ], + ) + def test_eval(test_input, expected): + assert eval(test_input) == expected + + :param values: Variable args of the values of the parameter set, in order. + + :param marks: + A single mark or a list of marks to be applied to this parameter set. + + :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( # 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 evaluable expression " + "where all names are substring-matched against test names " + "and their parent classes. Example: -k 'test_method or test_" + "other' matches all test functions and classes whose name " + "contains 'test_method' or 'test_other', while -k 'not test_method' " + "matches those that don't contain 'test_method' in their names. " + "-k 'not test_method and not test_other' will eliminate the matches. " + "Additionally keywords are matched to classes and functions " + "containing extra names in their 'extra_keyword_matches' set, " + "as well as functions which have names assigned directly to them. " + "The matching is case-insensitive.", + ) + + group._addoption( # private to use reserved lower-case short option + "-m", + action="store", + dest="markexpr", + default="", + metavar="MARKEXPR", + help="Only run tests matching given mark expression. " + "For example: -m 'mark1 and not mark2'.", + ) + + group.addoption( + "--markers", + action="store_true", + help="show markers (builtin, plugin and per-project ones).", + ) + + parser.addini("markers", "Register new markers for test functions", "linelist") + parser.addini(EMPTY_PARAMETERSET_OPTION, "Default marker for empty parametersets") + + +@hookimpl(tryfirst=True) +def pytest_cmdline_main(config: Config) -> int | ExitCode | None: + import _pytest.config + + if config.option.markers: + config._do_configure() + tw = _pytest.config.create_terminal_writer(config) + for line in config.getini("markers"): + parts = line.split(":", 1) + name = parts[0] + rest = parts[1] if len(parts) == 2 else "" + tw.write(f"@pytest.mark.{name}:", bold=True) + tw.line(rest) + tw.line() + config._ensure_unconfigure() + return 0 + + return None + + +@dataclasses.dataclass +class KeywordMatcher: + """A matcher for keywords. + + Given a list of names, matches any substring of one of these names. The + string inclusion check is case-insensitive. + + Will match on the name of colitem, including the names of its parents. + Only matches names of items which are either a :class:`Class` or a + :class:`Function`. + + Additionally, matches on names in the 'extra_keyword_matches' set of + any item, as well as names directly assigned to test functions. + """ + + __slots__ = ("_names",) + + _names: AbstractSet[str] + + @classmethod + def from_item(cls, item: Item) -> KeywordMatcher: + mapped_names = set() + + # Add the names of the current item and any parent items, + # except the Session and root Directory's which are not + # interesting for matching. + import pytest + + for node in item.listchain(): + if isinstance(node, pytest.Session): + continue + if isinstance(node, pytest.Directory) and isinstance( + node.parent, pytest.Session + ): + continue + mapped_names.add(node.name) + + # Add the names added as extra keywords to current or parent items. + mapped_names.update(item.listextrakeywords()) + + # Add the names attached to the current function through direct assignment. + function_obj = getattr(item, "function", None) + if function_obj: + mapped_names.update(function_obj.__dict__) + + # Add the markers to the keywords as we no longer handle them correctly. + mapped_names.update(mark.name for mark in item.iter_markers()) + + return cls(mapped_names) + + def __call__(self, subname: str, /, **kwargs: str | int | bool | None) -> bool: + if kwargs: + raise UsageError("Keyword expressions do not support call parameters.") + subname = subname.lower() + return any(subname in name.lower() for name in self._names) + + +def deselect_by_keyword(items: list[Item], config: Config) -> None: + keywordexpr = config.option.keyword.lstrip() + if not keywordexpr: + return + + expr = _parse_expression(keywordexpr, "Wrong expression passed to '-k'") + + remaining = [] + deselected = [] + for colitem in items: + if not expr.evaluate(KeywordMatcher.from_item(colitem)): + deselected.append(colitem) + else: + remaining.append(colitem) + + if deselected: + config.hook.pytest_deselected(items=deselected) + items[:] = remaining + + +@dataclasses.dataclass +class MarkMatcher: + """A matcher for markers which are present. + + Tries to match on any marker names, attached to the given colitem. + """ + + __slots__ = ("own_mark_name_mapping",) + + own_mark_name_mapping: dict[str, list[Mark]] + + @classmethod + def from_markers(cls, markers: Iterable[Mark]) -> MarkMatcher: + mark_name_mapping = collections.defaultdict(list) + for mark in markers: + mark_name_mapping[mark.name].append(mark) + return cls(mark_name_mapping) + + def __call__(self, name: str, /, **kwargs: str | int | bool | None) -> bool: + if not (matches := self.own_mark_name_mapping.get(name, [])): + return False + + for mark in matches: # 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: + matchexpr = config.option.markexpr + if not matchexpr: + return + + expr = _parse_expression(matchexpr, "Wrong expression passed to '-m'") + remaining: list[Item] = [] + deselected: list[Item] = [] + for item in items: + if expr.evaluate(MarkMatcher.from_markers(item.iter_markers())): + remaining.append(item) + else: + deselected.append(item) + if deselected: + config.hook.pytest_deselected(items=deselected) + items[:] = remaining + + +def _parse_expression(expr: str, exc_message: str) -> Expression: + try: + return Expression.compile(expr) + except 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: + deselect_by_keyword(items, config) + deselect_by_mark(items, config) + + +def pytest_configure(config: Config) -> None: + config.stash[old_mark_config_key] = MARK_GEN._config + MARK_GEN._config = config + + empty_parameterset = config.getini(EMPTY_PARAMETERSET_OPTION) + + if empty_parameterset not in ("skip", "xfail", "fail_at_collect", None, ""): + raise UsageError( + f"{EMPTY_PARAMETERSET_OPTION!s} must be one of skip, xfail or fail_at_collect" + f" but it is {empty_parameterset!r}" + ) + + +def pytest_unconfigure(config: Config) -> None: + MARK_GEN._config = config.stash.get(old_mark_config_key, None) diff --git a/py311/lib/python3.11/site-packages/_pytest/mark/expression.py b/py311/lib/python3.11/site-packages/_pytest/mark/expression.py new file mode 100644 index 0000000000000000000000000000000000000000..3bdbd03c2b55ca20aba1c56d49056a9cf13d4953 --- /dev/null +++ b/py311/lib/python3.11/site-packages/_pytest/mark/expression.py @@ -0,0 +1,353 @@ +r"""Evaluate match expressions, as used by `-k` and `-m`. + +The grammar is: + +expression: expr? EOF +expr: and_expr ('or' and_expr)* +and_expr: not_expr ('and' not_expr)* +not_expr: 'not' not_expr | '(' expr ')' | ident kwargs? + +ident: (\w|:|\+|-|\.|\[|\]|\\|/)+ +kwargs: ('(' name '=' value ( ', ' name '=' value )* ')') +name: a valid ident, but not a reserved keyword +value: (unescaped) string literal | (-)?[0-9]+ | 'False' | 'True' | 'None' + +The semantics are: + +- Empty expression evaluates to False. +- ident evaluates to True or False according to a provided matcher function. +- ident with parentheses and keyword arguments evaluates to True or False according to a provided matcher function. +- or/and/not evaluate according to the usual boolean semantics. +""" + +from __future__ import annotations + +import ast +from collections.abc import Iterator +from collections.abc import Mapping +from collections.abc import Sequence +import dataclasses +import enum +import keyword +import re +import types +from typing import Final +from typing import final +from typing import Literal +from typing import NoReturn +from typing import overload +from typing import Protocol + + +__all__ = [ + "Expression", + "ExpressionMatcher", +] + + +FILE_NAME: Final = "" + + +class TokenType(enum.Enum): + LPAREN = "left parenthesis" + RPAREN = "right parenthesis" + OR = "or" + AND = "and" + NOT = "not" + IDENT = "identifier" + EOF = "end of input" + EQUAL = "=" + STRING = "string literal" + COMMA = "," + + +@dataclasses.dataclass(frozen=True) +class Token: + __slots__ = ("pos", "type", "value") + type: TokenType + value: str + pos: int + + +class Scanner: + __slots__ = ("current", "input", "tokens") + + def __init__(self, input: str) -> None: + self.input = input + self.tokens = self.lex(input) + self.current = next(self.tokens) + + def lex(self, input: str) -> Iterator[Token]: + pos = 0 + while pos < len(input): + if input[pos] in (" ", "\t"): + pos += 1 + elif input[pos] == "(": + yield Token(TokenType.LPAREN, "(", pos) + pos += 1 + elif input[pos] == ")": + yield Token(TokenType.RPAREN, ")", pos) + pos += 1 + elif input[pos] == "=": + yield Token(TokenType.EQUAL, "=", pos) + pos += 1 + elif input[pos] == ",": + yield Token(TokenType.COMMA, ",", pos) + pos += 1 + elif (quote_char := input[pos]) in ("'", '"'): + end_quote_pos = input.find(quote_char, pos + 1) + if end_quote_pos == -1: + raise 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: + value = match.group(0) + if value == "or": + yield Token(TokenType.OR, value, pos) + elif value == "and": + yield Token(TokenType.AND, value, pos) + elif value == "not": + yield Token(TokenType.NOT, value, pos) + else: + yield Token(TokenType.IDENT, value, pos) + pos += len(value) + else: + raise SyntaxError( + f'unexpected character "{input[pos]}"', + (FILE_NAME, 1, pos + 1, input), + ) + yield Token(TokenType.EOF, "", pos) + + @overload + def accept(self, type: TokenType, *, reject: Literal[True]) -> Token: ... + + @overload + def accept( + self, type: TokenType, *, reject: Literal[False] = False + ) -> Token | None: ... + + def accept(self, type: TokenType, *, reject: bool = False) -> Token | None: + if self.current.type is type: + token = self.current + if token.type is not TokenType.EOF: + self.current = next(self.tokens) + return token + if reject: + self.reject((type,)) + return None + + def reject(self, expected: Sequence[TokenType]) -> NoReturn: + raise 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), + ) + + +# True, False and None are legal match expression identifiers, +# but illegal as Python identifiers. To fix this, this prefix +# is added to identifiers in the conversion to Python AST. +IDENT_PREFIX = "$" + + +def expression(s: Scanner) -> ast.Expression: + if s.accept(TokenType.EOF): + ret: ast.expr = ast.Constant(False) + else: + ret = expr(s) + s.accept(TokenType.EOF, reject=True) + return ast.fix_missing_locations(ast.Expression(ret)) + + +def expr(s: Scanner) -> ast.expr: + ret = and_expr(s) + while s.accept(TokenType.OR): + rhs = and_expr(s) + ret = ast.BoolOp(ast.Or(), [ret, rhs]) + return ret + + +def and_expr(s: Scanner) -> ast.expr: + ret = not_expr(s) + while s.accept(TokenType.AND): + rhs = not_expr(s) + ret = ast.BoolOp(ast.And(), [ret, rhs]) + return ret + + +def not_expr(s: Scanner) -> ast.expr: + if s.accept(TokenType.NOT): + return ast.UnaryOp(ast.Not(), not_expr(s)) + if s.accept(TokenType.LPAREN): + ret = expr(s) + s.accept(TokenType.RPAREN, reject=True) + return ret + ident = s.accept(TokenType.IDENT) + if ident: + name = ast.Name(IDENT_PREFIX + ident.value, ast.Load()) + if s.accept(TokenType.LPAREN): + ret = ast.Call(func=name, args=[], keywords=all_kwargs(s)) + s.accept(TokenType.RPAREN, reject=True) + else: + ret = name + return ret + + s.reject((TokenType.NOT, TokenType.LPAREN, TokenType.IDENT)) + + +BUILTIN_MATCHERS = {"True": True, "False": False, "None": None} + + +def single_kwarg(s: Scanner) -> ast.keyword: + keyword_name = s.accept(TokenType.IDENT, reject=True) + if not keyword_name.value.isidentifier(): + raise 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: ExpressionMatcher) -> None: + self.matcher = matcher + + def __getitem__(self, key: str) -> MatcherNameAdapter: + return MatcherNameAdapter(matcher=self.matcher, name=key[len(IDENT_PREFIX) :]) + + def __iter__(self) -> Iterator[str]: + raise NotImplementedError() + + def __len__(self) -> int: + raise NotImplementedError() + + +@final +class Expression: + """A compiled match expression as used by -k and -m. + + The expression can be evaluated against different matchers. + """ + + __slots__ = ("_code", "input") + + 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(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 = compile( + astexpr, + filename="", + mode="eval", + ) + return Expression(input, code) + + def evaluate(self, matcher: ExpressionMatcher) -> bool: + """Evaluate the match expression. + + :param matcher: + 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. + """ + return bool(eval(self._code, {"__builtins__": {}}, MatcherAdapter(matcher))) diff --git a/py311/lib/python3.11/site-packages/_pytest/mark/structures.py b/py311/lib/python3.11/site-packages/_pytest/mark/structures.py new file mode 100644 index 0000000000000000000000000000000000000000..16bb6d81119bd4914dbcabfd8102c9db03b1c227 --- /dev/null +++ b/py311/lib/python3.11/site-packages/_pytest/mark/structures.py @@ -0,0 +1,664 @@ +# 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 +from typing import Any +from typing import final +from typing import NamedTuple +from typing import overload +from typing import TYPE_CHECKING +from typing import TypeVar +import warnings + +from .._code import getfslineno +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 + + +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: + from ..nodes import Collector + + 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": + raise Collector.CollectError( + f"Empty parameter set in '{func.__name__}' at line {lineno + 1}" + ) + else: + raise LookupError(requested_mark) + return mark + + +class ParameterSet(NamedTuple): + """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: 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) 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: ParameterSet | Sequence[object] | object, + force_tuple: bool = False, + ) -> ParameterSet: + """Extract from an object or objects. + + :param parameterset: + A legacy style parameterset that may or may not be a tuple, + and may or may not be wrapped into a mess of mark objects. + + :param force_tuple: + Enforce tuple wrapping so single argument tuple values + don't get decomposed and break tests. + """ + if isinstance(parameterset, cls): + return parameterset + if force_tuple: + return cls.param(parameterset) + else: + # TODO: Refactor to fix this type-ignore. Currently the following + # passes type-checking but crashes: + # + # @pytest.mark.parametrize(('x', 'y'), [1, 2]) + # def test_foo(x, y): pass + return cls(parameterset, marks=[], id=None) # type: ignore[arg-type] + + @staticmethod + def _parse_parametrize_args( + argnames: str | Sequence[str], + argvalues: Iterable[ParameterSet | Sequence[object] | object], + *args, + **kwargs, + ) -> tuple[Sequence[str], bool]: + if isinstance(argnames, str): + argnames = [x.strip() for x in argnames.split(",") if x.strip()] + force_tuple = len(argnames) == 1 + else: + force_tuple = False + return argnames, force_tuple + + @staticmethod + def _parse_parametrize_parameters( + argvalues: Iterable[ParameterSet | Sequence[object] | object], + force_tuple: bool, + ) -> list[ParameterSet]: + return [ + ParameterSet.extract_from(x, force_tuple=force_tuple) for x in argvalues + ] + + @classmethod + def _for_parametrize( + cls, + argnames: str | Sequence[str], + argvalues: Iterable[ParameterSet | Sequence[object] | object], + func, + config: Config, + nodeid: str, + ) -> tuple[Sequence[str], list[ParameterSet]]: + argnames, force_tuple = cls._parse_parametrize_args(argnames, argvalues) + parameters = cls._parse_parametrize_parameters(argvalues, force_tuple) + del argvalues + + if parameters: + # Check all parameter sets have the correct number of values. + for param in parameters: + if len(param.values) != len(argnames): + msg = ( + '{nodeid}: in "parametrize" the number of names ({names_len}):\n' + " {names}\n" + "must be equal to the number of values ({values_len}):\n" + " {values}" + ) + fail( + msg.format( + nodeid=nodeid, + values=param.values, + names=argnames, + names_len=len(argnames), + values_len=len(param.values), + ), + pytrace=False, + ) + else: + # Empty parameter set (likely computed at runtime): create a single + # parameter set with NOTSET values, with the "empty parameter set" mark applied to it. + mark = get_empty_parameterset_mark(config, argnames, func) + parameters.append( + ParameterSet( + values=(NOTSET,) * len(argnames), marks=[mark], id="NOTSET" + ) + ) + return argnames, parameters + + +@final +@dataclasses.dataclass(frozen=True) +class Mark: + """A pytest mark.""" + + #: Name of the mark. + name: str + #: Positional arguments of the mark decorator. + args: tuple[Any, ...] + #: Keyword arguments of the mark decorator. + kwargs: Mapping[str, Any] + + #: Source Mark for ids with parametrize Marks. + _param_ids_from: Mark | None = dataclasses.field(default=None, repr=False) + #: Resolved/generated ids with parametrize Marks. + _param_ids_generated: Sequence[str] | None = dataclasses.field( + default=None, repr=False + ) + + def __init__( + self, + name: str, + args: tuple[Any, ...], + kwargs: Mapping[str, Any], + param_ids_from: Mark | None = None, + param_ids_generated: Sequence[str] | None = None, + *, + _ispytest: bool = False, + ) -> None: + """:meta private:""" + check_ispytest(_ispytest) + # Weirdness to bypass frozen=True. + object.__setattr__(self, "name", name) + object.__setattr__(self, "args", args) + object.__setattr__(self, "kwargs", kwargs) + object.__setattr__(self, "_param_ids_from", param_ids_from) + object.__setattr__(self, "_param_ids_generated", param_ids_generated) + + def _has_param_ids(self) -> bool: + return "ids" in self.kwargs or len(self.args) >= 4 + + def combined_with(self, other: Mark) -> Mark: + """Return a new Mark which is a combination of this + Mark and another Mark. + + Combines by appending args and merging kwargs. + + :param Mark other: The mark to combine with. + :rtype: Mark + """ + assert self.name == other.name + + # Remember source of ids with parametrize Marks. + param_ids_from: Mark | None = None + if self.name == "parametrize": + if other._has_param_ids(): + param_ids_from = other + elif self._has_param_ids(): + param_ids_from = self + + return Mark( + self.name, + self.args + other.args, + dict(self.kwargs, **other.kwargs), + param_ids_from=param_ids_from, + _ispytest=True, + ) + + +# A generic parameter designating an object to which a Mark may +# be applied -- a test function (callable) or class. +# Note: a lambda is not allowed, but this can't be represented. +Markable = TypeVar("Markable", bound=Callable[..., object] | type) + + +@dataclasses.dataclass +class MarkDecorator: + """A decorator for applying a mark on test functions and classes. + + ``MarkDecorators`` are created with ``pytest.mark``:: + + mark1 = pytest.mark.NAME # Simple MarkDecorator + mark2 = pytest.mark.NAME(name1=value) # Parametrized MarkDecorator + + and can then be applied as decorators to test functions:: + + @mark2 + def test_function(): + pass + + When a ``MarkDecorator`` is called, it does the following: + + 1. If called with a single class as its only positional argument and no + additional keyword arguments, it attaches the mark to the class so it + gets applied automatically to all test cases found in that class. + + 2. If called with a single function as its only positional argument and + no additional keyword arguments, it attaches the mark to the function, + containing all the arguments already stored internally in the + ``MarkDecorator``. + + 3. When called in any other case, it returns a new ``MarkDecorator`` + instance with the original ``MarkDecorator``'s content updated with + the arguments passed to this call. + + Note: The rules above prevent a ``MarkDecorator`` from storing only a + single function or class reference as its positional argument with no + additional keyword or positional arguments. You can work around this by + using `with_args()`. + """ + + mark: Mark + + def __init__(self, mark: Mark, *, _ispytest: bool = False) -> None: + """:meta private:""" + check_ispytest(_ispytest) + self.mark = mark + + @property + def name(self) -> str: + """Alias for mark.name.""" + return self.mark.name + + @property + def args(self) -> tuple[Any, ...]: + """Alias for mark.args.""" + return self.mark.args + + @property + def kwargs(self) -> Mapping[str, Any]: + """Alias for mark.kwargs.""" + return self.mark.kwargs + + @property + def markname(self) -> str: + """:meta private:""" + return self.name # for backward-compat (2.4.1 had this attr) + + def with_args(self, *args: object, **kwargs: object) -> MarkDecorator: + """Return a MarkDecorator with extra arguments added. + + Unlike calling the MarkDecorator, with_args() can be used even + if the sole argument is a callable/class. + """ + mark = Mark(self.name, args, kwargs, _ispytest=True) + return MarkDecorator(self.mark.combined_with(mark), _ispytest=True) + + # Type ignored because the overloads overlap with an incompatible + # return type. Not much we can do about that. Thankfully mypy picks + # the first match so it works out even if we break the rules. + @overload + def __call__(self, arg: Markable) -> Markable: # type: ignore[overload-overlap] + pass + + @overload + def __call__(self, *args: object, **kwargs: object) -> MarkDecorator: + pass + + def __call__(self, *args: object, **kwargs: object): + """Call the MarkDecorator.""" + if args and not kwargs: + func = args[0] + is_class = inspect.isclass(func) + # 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: object | type, + *, + consider_mro: bool = True, +) -> list[Mark]: + """Obtain the unpacked marks that are stored on an object. + + If obj is a class and consider_mro is true, return marks applied to + this class and all of its super-classes in MRO order. If consider_mro + is false, only return marks applied directly to this class. + """ + if isinstance(obj, type): + if not consider_mro: + mark_lists = [obj.__dict__.get("pytestmark", [])] + else: + mark_lists = [ + x.__dict__.get("pytestmark", []) for x in reversed(obj.__mro__) + ] + mark_list = [] + for item in mark_lists: + if isinstance(item, list): + mark_list.extend(item) + else: + mark_list.append(item) + else: + mark_attribute = getattr(obj, "pytestmark", []) + if isinstance(mark_attribute, list): + mark_list = mark_attribute + else: + mark_list = [mark_attribute] + return list(normalize_mark_list(mark_list)) + + +def normalize_mark_list( + mark_list: Iterable[Mark | MarkDecorator], +) -> Iterable[Mark]: + """ + Normalize an iterable of Mark or MarkDecorator objects into a list of marks + by retrieving the `mark` attribute on MarkDecorator instances. + + :param mark_list: marks to normalize + :returns: A new list of the extracted Mark objects + """ + for mark in mark_list: + mark_obj = getattr(mark, "mark", mark) + if not isinstance(mark_obj, Mark): + raise TypeError(f"got {mark_obj!r} instead of Mark") + yield mark_obj + + +def store_mark(obj, mark: Mark, *, stacklevel: int = 2) -> None: + """Store a Mark on an object. + + This is used to implement the Mark declarations/decorators correctly. + """ + assert isinstance(mark, Mark), mark + + from ..fixtures import getfixturemarker + + if getfixturemarker(obj) is not None: + warnings.warn(MARKED_FIXTURE, stacklevel=stacklevel) + + # Always reassign name to avoid updating pytestmark in a reference that + # was only borrowed. + obj.pytestmark = [*get_unpacked_marks(obj, consider_mro=False), mark] + + +# Typing for builtin pytest marks. This is cheating; it gives builtin marks +# special privilege, and breaks modularity. But practicality beats purity... +if TYPE_CHECKING: + + class _SkipMarkDecorator(MarkDecorator): + @overload # type: ignore[override,no-overload-impl] + def __call__(self, arg: Markable) -> Markable: ... + + @overload + def __call__(self, reason: str = ...) -> MarkDecorator: ... + + class _SkipifMarkDecorator(MarkDecorator): + def __call__( # type: ignore[override] + self, + condition: str | bool = ..., + *conditions: str | bool, + reason: str = ..., + ) -> MarkDecorator: ... + + class _XfailMarkDecorator(MarkDecorator): + @overload # type: ignore[override,no-overload-impl] + def __call__(self, arg: Markable) -> Markable: ... + + @overload + def __call__( + self, + condition: str | bool = False, + *conditions: str | bool, + reason: str = ..., + run: bool = ..., + raises: None + | type[BaseException] + | tuple[type[BaseException], ...] + | AbstractRaises[BaseException] = ..., + strict: bool = ..., + ) -> MarkDecorator: ... + + class _ParametrizeMarkDecorator(MarkDecorator): + def __call__( # type: ignore[override] + self, + argnames: str | Sequence[str], + argvalues: Iterable[ParameterSet | Sequence[object] | object], + *, + indirect: bool | Sequence[str] = ..., + ids: Iterable[None | str | float | int | bool] + | Callable[[Any], object | None] + | None = ..., + scope: _ScopeName | None = ..., + ) -> MarkDecorator: ... + + class _UsefixturesMarkDecorator(MarkDecorator): + def __call__(self, *fixtures: str) -> MarkDecorator: # type: ignore[override] + ... + + class _FilterwarningsMarkDecorator(MarkDecorator): + def __call__(self, *filters: str) -> MarkDecorator: # type: ignore[override] + ... + + +@final +class MarkGenerator: + """Factory for :class:`MarkDecorator` objects - exposed as + a ``pytest.mark`` singleton instance. + + Example:: + + import pytest + + + @pytest.mark.slowtest + def test_function(): + pass + + applies a 'slowtest' :class:`Mark` on ``test_function``. + """ + + # See TYPE_CHECKING above. + if TYPE_CHECKING: + skip: _SkipMarkDecorator + skipif: _SkipifMarkDecorator + xfail: _XfailMarkDecorator + parametrize: _ParametrizeMarkDecorator + usefixtures: _UsefixturesMarkDecorator + filterwarnings: _FilterwarningsMarkDecorator + + def __init__(self, *, _ispytest: bool = False) -> None: + check_ispytest(_ispytest) + self._config: Config | None = None + self._markers: set[str] = set() + + def __getattr__(self, name: str) -> MarkDecorator: + """Generate a new :class:`MarkDecorator` with the given name.""" + if name[0] == "_": + raise AttributeError("Marker name must NOT start with underscore") + + if self._config is not None: + # We store a set of markers as a performance optimisation - if a mark + # name is in the set we definitely know it, but a mark may be known and + # not in the set. We therefore start by updating the set! + if name not in self._markers: + for line in self._config.getini("markers"): + # example lines: "skipif(condition): skip the given test if..." + # or "hypothesis: tests which use Hypothesis", so to get the + # marker name we split on both `:` and `(`. + marker = line.split(":")[0].split("(")[0].strip() + self._markers.add(marker) + + # If the name is not in the set of known marks after updating, + # then it really is time to issue a warning or an error. + if name not in self._markers: + # 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( + f"Unknown pytest.mark.{name} - is this a typo? You can register " + "custom marks to avoid this warning - for details, see " + "https://docs.pytest.org/en/stable/how-to/mark.html", + PytestUnknownMarkWarning, + 2, + ) + + return MarkDecorator(Mark(name, (), {}, _ispytest=True), _ispytest=True) + + +MARK_GEN = MarkGenerator(_ispytest=True) + + +@final +class NodeKeywords(MutableMapping[str, Any]): + __slots__ = ("_markers", "node", "parent") + + def __init__(self, node: Node) -> None: + self.node = node + self.parent = node.parent + self._markers = {node.name: True} + + def __getitem__(self, key: str) -> Any: + try: + return self._markers[key] + except KeyError: + if self.parent is None: + raise + return self.parent.keywords[key] + + def __setitem__(self, key: str, value: Any) -> None: + self._markers[key] = value + + # Note: we could've avoided explicitly implementing some of the methods + # below and use the collections.abc fallback, but that would be slow. + + def __contains__(self, key: object) -> bool: + return key in self._markers or ( + self.parent is not None and key in self.parent.keywords + ) + + def update( # type: ignore[override] + self, + other: Mapping[str, Any] | Iterable[tuple[str, Any]] = (), + **kwds: Any, + ) -> None: + self._markers.update(other) + self._markers.update(kwds) + + def __delitem__(self, key: str) -> None: + raise ValueError("cannot delete key in keywords dict") + + def __iter__(self) -> Iterator[str]: + # Doesn't need to be fast. + yield from self._markers + if self.parent is not None: + for keyword in self.parent.keywords: + # self._marks and self.parent.keywords can have duplicates. + if keyword not in self._markers: + yield keyword + + def __len__(self) -> int: + # Doesn't need to be fast. + return sum(1 for keyword in self) + + def __repr__(self) -> str: + return f"" diff --git a/py311/lib/python3.11/site-packages/_virtualenv.pth b/py311/lib/python3.11/site-packages/_virtualenv.pth new file mode 100644 index 0000000000000000000000000000000000000000..74d2eb6578fd774c6d8e324cd2fad5efb8beb747 --- /dev/null +++ b/py311/lib/python3.11/site-packages/_virtualenv.pth @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:69ac3d8f27e679c81b94ab30b3b56e9cd138219b1ba94a1fa3606d5a76a1433d +size 18 diff --git a/py311/lib/python3.11/site-packages/aiohttp-3.13.3.dist-info/licenses/LICENSE.txt b/py311/lib/python3.11/site-packages/aiohttp-3.13.3.dist-info/licenses/LICENSE.txt new file mode 100644 index 0000000000000000000000000000000000000000..e497a322f2091d022983b9c5c043082ab61d1a8c --- /dev/null +++ b/py311/lib/python3.11/site-packages/aiohttp-3.13.3.dist-info/licenses/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/py311/lib/python3.11/site-packages/aiohttp-3.13.3.dist-info/licenses/vendor/llhttp/LICENSE b/py311/lib/python3.11/site-packages/aiohttp-3.13.3.dist-info/licenses/vendor/llhttp/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..6c1512dd6bcd6de314673f8c2c7ff3fd6139f28e --- /dev/null +++ b/py311/lib/python3.11/site-packages/aiohttp-3.13.3.dist-info/licenses/vendor/llhttp/LICENSE @@ -0,0 +1,22 @@ +This software is licensed under the MIT License. + +Copyright Fedor Indutny, 2018. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to permit +persons to whom the Software is furnished to do so, subject to the +following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/py311/lib/python3.11/site-packages/aiohttp/_http_parser.cpython-311-x86_64-linux-gnu.so b/py311/lib/python3.11/site-packages/aiohttp/_http_parser.cpython-311-x86_64-linux-gnu.so new file mode 100644 index 0000000000000000000000000000000000000000..1d2a2768857926d4008eba0e6b645670fac72723 --- /dev/null +++ b/py311/lib/python3.11/site-packages/aiohttp/_http_parser.cpython-311-x86_64-linux-gnu.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3ce6ed2443a328306aae49a071c6c91dc6efbbf2a5feec4f881e80a30ce9f367 +size 2794568 diff --git a/py311/lib/python3.11/site-packages/aiohttp/_http_writer.cpython-311-x86_64-linux-gnu.so b/py311/lib/python3.11/site-packages/aiohttp/_http_writer.cpython-311-x86_64-linux-gnu.so new file mode 100644 index 0000000000000000000000000000000000000000..706d07f0f9bc8c9051068a4d35841d2b4a59e771 --- /dev/null +++ b/py311/lib/python3.11/site-packages/aiohttp/_http_writer.cpython-311-x86_64-linux-gnu.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:56993d1f8e94843db09b7540f1800ccf6db512ac93e6805d9264774d61fdb26d +size 533008 diff --git a/py311/lib/python3.11/site-packages/attr/__pycache__/_make.cpython-311.pyc b/py311/lib/python3.11/site-packages/attr/__pycache__/_make.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1d0b4a66580092d40288a84f37f3784112fa1454 --- /dev/null +++ b/py311/lib/python3.11/site-packages/attr/__pycache__/_make.cpython-311.pyc @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:eb3f035bf96d184612dfead133cd8f18b2f759528538643a5525a7b574417fa5 +size 117331 diff --git a/py311/lib/python3.11/site-packages/botocore/__pycache__/credentials.cpython-311.pyc b/py311/lib/python3.11/site-packages/botocore/__pycache__/credentials.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dd115d00254086f92f64c0e79b9d4bb2c62c330f --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/__pycache__/credentials.cpython-311.pyc @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7e82d59ffaecd4e09202ee636e5ecf917e29b2f44c815ceac1c6493b7c2dfbe2 +size 115413 diff --git a/py311/lib/python3.11/site-packages/botocore/__pycache__/utils.cpython-311.pyc b/py311/lib/python3.11/site-packages/botocore/__pycache__/utils.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..27d893eb3611f9631a66f8e2f7ecffef7f3d2628 --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/__pycache__/utils.cpython-311.pyc @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2d457c553ee25dc7da7f9c29a30f9c500c1202fafcfa02bea801e524c7ecdd49 +size 162076 diff --git a/py311/lib/python3.11/site-packages/botocore/data/amplify/2017-07-25/endpoint-rule-set-1.json.gz b/py311/lib/python3.11/site-packages/botocore/data/amplify/2017-07-25/endpoint-rule-set-1.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..784641d71f68c9034fc351247851406ae0b9bef3 --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/amplify/2017-07-25/endpoint-rule-set-1.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e62d92b6a5863f70018668ec98a70e78b0e1c563c0754f93a497ec39fdd07c0d +size 1149 diff --git a/py311/lib/python3.11/site-packages/botocore/data/amplify/2017-07-25/service-2.json.gz b/py311/lib/python3.11/site-packages/botocore/data/amplify/2017-07-25/service-2.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..0882cee1bbb1f40d01873b4cbf18771624aedf38 --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/amplify/2017-07-25/service-2.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f40a1198693f574140fe804fbd14ace63fd518eeb5b7be57efb5c4c3dc99eb3d +size 17604 diff --git a/py311/lib/python3.11/site-packages/botocore/data/amplifybackend/2020-08-11/endpoint-rule-set-1.json.gz b/py311/lib/python3.11/site-packages/botocore/data/amplifybackend/2020-08-11/endpoint-rule-set-1.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..dcf3965ffeec0911d667402a2393ec3515923495 --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/amplifybackend/2020-08-11/endpoint-rule-set-1.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:10dfa4982602a038f99ef9fe91c8aa03d4512ac4bb414d4c0ca2d6f07113b64c +size 1154 diff --git a/py311/lib/python3.11/site-packages/botocore/data/amplifybackend/2020-08-11/service-2.json.gz b/py311/lib/python3.11/site-packages/botocore/data/amplifybackend/2020-08-11/service-2.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..d1daba980e2dd3c5ed4ace96445432455fa61149 --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/amplifybackend/2020-08-11/service-2.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2d40e6215f974ceb797dfa8e28fabcdc62bcbbbc799f8ad0453b5bad1188853c +size 10990 diff --git a/py311/lib/python3.11/site-packages/botocore/data/apigateway/2015-07-09/endpoint-rule-set-1.json.gz b/py311/lib/python3.11/site-packages/botocore/data/apigateway/2015-07-09/endpoint-rule-set-1.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..ad785ca20df2595daed7b5087f59deaf47ed5233 --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/apigateway/2015-07-09/endpoint-rule-set-1.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:84ecca8268d6ceb6b8ba748f8e0ad94e2edd3cc6b424c2e68d9d498759fd5c16 +size 1151 diff --git a/py311/lib/python3.11/site-packages/botocore/data/apigateway/2015-07-09/service-2.json.gz b/py311/lib/python3.11/site-packages/botocore/data/apigateway/2015-07-09/service-2.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..995e3345ef8b0b538de1f56a137610b7ec375450 --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/apigateway/2015-07-09/service-2.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:16a9e725c4822ce33e247b0edf7faeb0d1f61447da9945e709c270d14ceaaf39 +size 39734 diff --git a/py311/lib/python3.11/site-packages/botocore/data/appfabric/2023-05-19/endpoint-rule-set-1.json.gz b/py311/lib/python3.11/site-packages/botocore/data/appfabric/2023-05-19/endpoint-rule-set-1.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..066bdc8c771f5e03b5bbc6d95586d328c8ca9757 --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/appfabric/2023-05-19/endpoint-rule-set-1.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6f2c354feea719cd2f23676bc4d662aceacb30412ee5cd2e6bc66ff05b1eead3 +size 1296 diff --git a/py311/lib/python3.11/site-packages/botocore/data/appfabric/2023-05-19/service-2.json.gz b/py311/lib/python3.11/site-packages/botocore/data/appfabric/2023-05-19/service-2.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..ad4c33437e8e360e59cfb4e95007269f109c5a63 --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/appfabric/2023-05-19/service-2.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c1e2ea3697ae8ebba80ee0d151f91254d2e39acffca21b16e0566d154b886642 +size 8601 diff --git a/py311/lib/python3.11/site-packages/botocore/data/appintegrations/2020-07-29/endpoint-rule-set-1.json.gz b/py311/lib/python3.11/site-packages/botocore/data/appintegrations/2020-07-29/endpoint-rule-set-1.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..8d41c88813b8696a9d252305fcabb4a50b489a68 --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/appintegrations/2020-07-29/endpoint-rule-set-1.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:546481e87ab7163b8aa8fe9302a94b4ebb496918d4f2379ff65dee8474f9b06e +size 1153 diff --git a/py311/lib/python3.11/site-packages/botocore/data/appintegrations/2020-07-29/service-2.json.gz b/py311/lib/python3.11/site-packages/botocore/data/appintegrations/2020-07-29/service-2.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..da2e720a77301b0c8e12c3a364a030b9c25e719d --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/appintegrations/2020-07-29/service-2.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:20061e0dacf4448bed9d32ce21f7606ad75dc0f6001e10842380cb9fb5aa122a +size 6981 diff --git a/py311/lib/python3.11/site-packages/botocore/data/application-autoscaling/2016-02-06/endpoint-rule-set-1.json.gz b/py311/lib/python3.11/site-packages/botocore/data/application-autoscaling/2016-02-06/endpoint-rule-set-1.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..b20b0088ce57053c77561a14a5753ce7f9c45127 --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/application-autoscaling/2016-02-06/endpoint-rule-set-1.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6933276d05b2a6a1c51b73889c7a626e1f9b49fd417c7fe0b4ea0b96814cbf01 +size 1244 diff --git a/py311/lib/python3.11/site-packages/botocore/data/application-autoscaling/2016-02-06/service-2.json.gz b/py311/lib/python3.11/site-packages/botocore/data/application-autoscaling/2016-02-06/service-2.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..f4ca8af69398306148194ccee8161679cadeab4b --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/application-autoscaling/2016-02-06/service-2.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fbb3f99e6b75fb46bdcfd091d147de0538fcd41fc50a9f71bf65e1f03bd6a22e +size 24469 diff --git a/py311/lib/python3.11/site-packages/botocore/data/applicationcostprofiler/2020-09-10/endpoint-rule-set-1.json.gz b/py311/lib/python3.11/site-packages/botocore/data/applicationcostprofiler/2020-09-10/endpoint-rule-set-1.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..179127e536ba1f9e83b773f6c1f774699b6ea3ad --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/applicationcostprofiler/2020-09-10/endpoint-rule-set-1.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dc9b4567f95184c4653e7cb1c22109ee90a2e499ab535abf03aafa2eac6ca9c4 +size 1164 diff --git a/py311/lib/python3.11/site-packages/botocore/data/applicationcostprofiler/2020-09-10/service-2.json.gz b/py311/lib/python3.11/site-packages/botocore/data/applicationcostprofiler/2020-09-10/service-2.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..8f0efe1b65563ce01d003175684a50a2f09db3ab --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/applicationcostprofiler/2020-09-10/service-2.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:30528dba0960c2ebca8bb83e96b199df899a97f4064c461bbd1b7ba6c677e04b +size 2850 diff --git a/py311/lib/python3.11/site-packages/botocore/data/appmesh/2018-10-01/endpoint-rule-set-1.json.gz b/py311/lib/python3.11/site-packages/botocore/data/appmesh/2018-10-01/endpoint-rule-set-1.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..a1fd6ddc180cd099601c1bdb9ffdffdf0696d149 --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/appmesh/2018-10-01/endpoint-rule-set-1.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:03f02dd67693a2c11f93739d16b1ebf3a788fbc11263d546b0bc003d6ea913bb +size 1289 diff --git a/py311/lib/python3.11/site-packages/botocore/data/appmesh/2018-10-01/service-2.json.gz b/py311/lib/python3.11/site-packages/botocore/data/appmesh/2018-10-01/service-2.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..2f7b73827208f45dc00162e8be189cdb7e39c4a6 --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/appmesh/2018-10-01/service-2.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:58594719d888cac2a01fd3613ffc44074e91331339f066e66bb1a73654928599 +size 7902 diff --git a/py311/lib/python3.11/site-packages/botocore/data/appmesh/2019-01-25/endpoint-rule-set-1.json.gz b/py311/lib/python3.11/site-packages/botocore/data/appmesh/2019-01-25/endpoint-rule-set-1.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..12d4eac6d4058aff15f4f21b973a1adfb9ae41dd --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/appmesh/2019-01-25/endpoint-rule-set-1.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0ca8dc124807c83b718e35e7009c86ad36854809ed0f292ef65ee87443cb6ce2 +size 1149 diff --git a/py311/lib/python3.11/site-packages/botocore/data/appmesh/2019-01-25/service-2.json.gz b/py311/lib/python3.11/site-packages/botocore/data/appmesh/2019-01-25/service-2.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..f0e9b4652aebf1d1a5ecee265bf0735a03039b3a --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/appmesh/2019-01-25/service-2.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1e196e224ef60c996db7acf2831979569b863fa3c7feb61ce9af497a3bf4638d +size 23271 diff --git a/py311/lib/python3.11/site-packages/botocore/data/autoscaling/2011-01-01/endpoint-rule-set-1.json.gz b/py311/lib/python3.11/site-packages/botocore/data/autoscaling/2011-01-01/endpoint-rule-set-1.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..5b4ce0462f85e7daf030c3c023881bb617cc2b65 --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/autoscaling/2011-01-01/endpoint-rule-set-1.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:249c29fef71745e9008ace8b31ec047b89d04fbc24aa2ffda49b7d01daa7abdc +size 1236 diff --git a/py311/lib/python3.11/site-packages/botocore/data/autoscaling/2011-01-01/service-2.json.gz b/py311/lib/python3.11/site-packages/botocore/data/autoscaling/2011-01-01/service-2.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..bd6e3e124796b317aa2cc6bd373b439a9a7a0c56 --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/autoscaling/2011-01-01/service-2.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:215a10ca11f9a08203bbd92967da017e16716d52c8119619ac132fc4e1e52544 +size 63462 diff --git a/py311/lib/python3.11/site-packages/botocore/data/backupsearch/2018-05-10/endpoint-rule-set-1.json.gz b/py311/lib/python3.11/site-packages/botocore/data/backupsearch/2018-05-10/endpoint-rule-set-1.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..49ba5537ad7011d99faf67b97a3bb271606ab9b0 --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/backupsearch/2018-05-10/endpoint-rule-set-1.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7d7b81766fcb76755d37e601cfc24db73feb0c4ac8312f756837abc6647a2af6 +size 837 diff --git a/py311/lib/python3.11/site-packages/botocore/data/backupsearch/2018-05-10/service-2.json.gz b/py311/lib/python3.11/site-packages/botocore/data/backupsearch/2018-05-10/service-2.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..60ccff72c59fdc2c1ae0cd545919d4269b54fa28 --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/backupsearch/2018-05-10/service-2.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b0ce0c6778a4405db5fd254c41189d67ff669e346bfb4e244c345bc75322f700 +size 7506 diff --git a/py311/lib/python3.11/site-packages/botocore/data/bedrock-agentcore-control/2023-06-05/endpoint-rule-set-1.json.gz b/py311/lib/python3.11/site-packages/botocore/data/bedrock-agentcore-control/2023-06-05/endpoint-rule-set-1.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..de8c88ea6b95ffebd331b28fda11093cd2d624f0 --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/bedrock-agentcore-control/2023-06-05/endpoint-rule-set-1.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:466fb62b66d7a68e8bc43839ad8318a7e0c71ed5302361ad5ac3001ca378c4ba +size 1312 diff --git a/py311/lib/python3.11/site-packages/botocore/data/bedrock-agentcore-control/2023-06-05/service-2.json.gz b/py311/lib/python3.11/site-packages/botocore/data/bedrock-agentcore-control/2023-06-05/service-2.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..e638640e53a4509eb43ccd49c5be4f81a306388b --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/bedrock-agentcore-control/2023-06-05/service-2.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6262facb2a12c52afe4a54e06277423e1768a62858d4ee0657a9e5ea39ec4d86 +size 51693 diff --git a/py311/lib/python3.11/site-packages/botocore/data/bedrock-agentcore/2024-02-28/endpoint-rule-set-1.json.gz b/py311/lib/python3.11/site-packages/botocore/data/bedrock-agentcore/2024-02-28/endpoint-rule-set-1.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..8b62aa77898156f6f88ff0a2fcffffaa9d511049 --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/bedrock-agentcore/2024-02-28/endpoint-rule-set-1.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:43347e60fe972dba1e604f25d58e1a98552b84599c3fd571b70b88dec10cdf92 +size 1306 diff --git a/py311/lib/python3.11/site-packages/botocore/data/bedrock-agentcore/2024-02-28/service-2.json.gz b/py311/lib/python3.11/site-packages/botocore/data/bedrock-agentcore/2024-02-28/service-2.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..69742950b55b813c409456bbb04cf9600eb9eb87 --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/bedrock-agentcore/2024-02-28/service-2.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:61f03e5d76105474bcb916c7ece929f276f5624e171260ae664d7859241f9ce3 +size 22140 diff --git a/py311/lib/python3.11/site-packages/botocore/data/braket/2019-09-01/endpoint-rule-set-1.json.gz b/py311/lib/python3.11/site-packages/botocore/data/braket/2019-09-01/endpoint-rule-set-1.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..9d91701582b4c851860c9d6b0a05f3faea7ecd1f --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/braket/2019-09-01/endpoint-rule-set-1.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:31f73e23496d686613e9bc333b63a29823e8398063522afd741f53898f157f61 +size 1147 diff --git a/py311/lib/python3.11/site-packages/botocore/data/braket/2019-09-01/service-2.json.gz b/py311/lib/python3.11/site-packages/botocore/data/braket/2019-09-01/service-2.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..ce5358b7ce8b593dc9a9c73d06d55be3f51d5a8f --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/braket/2019-09-01/service-2.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9a021bd15ad83dc22cfa95961bfa9fd71e752641fff8892c1e5e59a491c24bf6 +size 11429 diff --git a/py311/lib/python3.11/site-packages/botocore/data/budgets/2016-10-20/endpoint-rule-set-1.json.gz b/py311/lib/python3.11/site-packages/botocore/data/budgets/2016-10-20/endpoint-rule-set-1.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..c995c5bc63d3f23085495e66c1aba5a985b48603 --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/budgets/2016-10-20/endpoint-rule-set-1.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:85df54d12bdea6826410c39353703e39e57d9d551dbc1c432fc7a72c1280d98b +size 1791 diff --git a/py311/lib/python3.11/site-packages/botocore/data/budgets/2016-10-20/service-2.json.gz b/py311/lib/python3.11/site-packages/botocore/data/budgets/2016-10-20/service-2.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..b489e16aac13d7c5815264fb7d90ce584a234d64 --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/budgets/2016-10-20/service-2.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f0d5851986f9a40e7bc445caa5dd7a2afc1ee5b4661cd1b3fa453fe44fa5861b +size 14621 diff --git a/py311/lib/python3.11/site-packages/botocore/data/cloudcontrol/2021-09-30/endpoint-rule-set-1.json.gz b/py311/lib/python3.11/site-packages/botocore/data/cloudcontrol/2021-09-30/endpoint-rule-set-1.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..9733de81357e8c247bde832b25591ae5ed3b6272 --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/cloudcontrol/2021-09-30/endpoint-rule-set-1.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:08f0dd90a68bb75d533928ab956455305f958d5d400a86cfdb883ae31aebac83 +size 1154 diff --git a/py311/lib/python3.11/site-packages/botocore/data/cloudcontrol/2021-09-30/service-2.json.gz b/py311/lib/python3.11/site-packages/botocore/data/cloudcontrol/2021-09-30/service-2.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..6f4197a587869574b5f7b8d62cef7eb5d6a8a99f --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/cloudcontrol/2021-09-30/service-2.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:aba24a0fb9c496ac4e37c7b94959511497cca76068ce630a05025ed2f32ddb29 +size 6492 diff --git a/py311/lib/python3.11/site-packages/botocore/data/cloudhsmv2/2017-04-28/endpoint-rule-set-1.json.gz b/py311/lib/python3.11/site-packages/botocore/data/cloudhsmv2/2017-04-28/endpoint-rule-set-1.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..ca24a48d0ae8234cbb094e75f066d0ab9c7de988 --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/cloudhsmv2/2017-04-28/endpoint-rule-set-1.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ae97b6133e8b7657f429f1ddb29b75ebeacdb29570df7e09938607876b727463 +size 1242 diff --git a/py311/lib/python3.11/site-packages/botocore/data/cloudhsmv2/2017-04-28/service-2.json.gz b/py311/lib/python3.11/site-packages/botocore/data/cloudhsmv2/2017-04-28/service-2.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..b44639e5de7b41240b931185b3c124c0e90a2720 --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/cloudhsmv2/2017-04-28/service-2.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:270485c8e587496acc141e277ce58ecaa0d3d5f89f878497e798b87bf1b83c39 +size 8056 diff --git a/py311/lib/python3.11/site-packages/botocore/data/cloudwatch/2010-08-01/endpoint-rule-set-1.json.gz b/py311/lib/python3.11/site-packages/botocore/data/cloudwatch/2010-08-01/endpoint-rule-set-1.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..94dcc81f03d4fa8e6f26defff0f610dbf6b03abe --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/cloudwatch/2010-08-01/endpoint-rule-set-1.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:60bb7c92a6bf9629a28e6aebb7102ae14702315972e396309c18b901e4f4c0cc +size 1415 diff --git a/py311/lib/python3.11/site-packages/botocore/data/cloudwatch/2010-08-01/service-2.json.gz b/py311/lib/python3.11/site-packages/botocore/data/cloudwatch/2010-08-01/service-2.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..0660363792cffdc03de3bccbfda0768c270266bb --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/cloudwatch/2010-08-01/service-2.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:394d6488d4b4d2a2882823774630aa55775122519988ef078914887711841e3c +size 41274 diff --git a/py311/lib/python3.11/site-packages/botocore/data/codecommit/2015-04-13/endpoint-rule-set-1.json.gz b/py311/lib/python3.11/site-packages/botocore/data/codecommit/2015-04-13/endpoint-rule-set-1.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..90f18e3d5eafa69ab25687e64f86a6d69ae33566 --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/codecommit/2015-04-13/endpoint-rule-set-1.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:09561696c8a2186adefc66364c1c757b39648d6543e3bd38040bd24c4c68dbd8 +size 1151 diff --git a/py311/lib/python3.11/site-packages/botocore/data/codecommit/2015-04-13/service-2.json.gz b/py311/lib/python3.11/site-packages/botocore/data/codecommit/2015-04-13/service-2.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..d79244c0266a5ef9906ea9575c2c11b439b61eae --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/codecommit/2015-04-13/service-2.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:94e91ec9d0beab6b0ddcb75424c4a8892a4f6a4273ea4842654c435be96422c4 +size 40935 diff --git a/py311/lib/python3.11/site-packages/botocore/data/codeguru-reviewer/2019-09-19/endpoint-rule-set-1.json.gz b/py311/lib/python3.11/site-packages/botocore/data/codeguru-reviewer/2019-09-19/endpoint-rule-set-1.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..288ea1e0557315f5e5309b42b75f6f4b7331a136 --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/codeguru-reviewer/2019-09-19/endpoint-rule-set-1.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7fd2b961c5be0c60ccb40d191747b97ab03541558034fd2a8fd41d283cebefad +size 1157 diff --git a/py311/lib/python3.11/site-packages/botocore/data/codeguru-reviewer/2019-09-19/service-2.json.gz b/py311/lib/python3.11/site-packages/botocore/data/codeguru-reviewer/2019-09-19/service-2.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..3336c5f592c0b794ae0d1669f9cdac245980916e --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/codeguru-reviewer/2019-09-19/service-2.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6716c8d24bc012b365d340b1ee5743d9d9f6f41f81b76971c73facc027d58b0e +size 11785 diff --git a/py311/lib/python3.11/site-packages/botocore/data/connect-contact-lens/2020-08-21/endpoint-rule-set-1.json.gz b/py311/lib/python3.11/site-packages/botocore/data/connect-contact-lens/2020-08-21/endpoint-rule-set-1.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..db8b94788e87fe6d3763a5e33af4771c5d5bb984 --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/connect-contact-lens/2020-08-21/endpoint-rule-set-1.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:258af3d079d15f546292c3f76793fe83485ffb5543452e0f117f3f5cf67a7562 +size 1152 diff --git a/py311/lib/python3.11/site-packages/botocore/data/connect-contact-lens/2020-08-21/service-2.json.gz b/py311/lib/python3.11/site-packages/botocore/data/connect-contact-lens/2020-08-21/service-2.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..18864ceb152c68578c0853803492eeb534a988da --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/connect-contact-lens/2020-08-21/service-2.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ae588fdfd55410bb980e01596d63a9d0efdc7230346e6c5e7362c47c4fab17ff +size 3162 diff --git a/py311/lib/python3.11/site-packages/botocore/data/connectcampaignsv2/2024-04-23/endpoint-rule-set-1.json.gz b/py311/lib/python3.11/site-packages/botocore/data/connectcampaignsv2/2024-04-23/endpoint-rule-set-1.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..7584c098c2b2a8eb597176d6e90c61f962b41914 --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/connectcampaignsv2/2024-04-23/endpoint-rule-set-1.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c278a7275aa660917738d0974f3ba8a916db13920efcf72cb6163504a7eb56b1 +size 1306 diff --git a/py311/lib/python3.11/site-packages/botocore/data/connectcampaignsv2/2024-04-23/service-2.json.gz b/py311/lib/python3.11/site-packages/botocore/data/connectcampaignsv2/2024-04-23/service-2.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..54f9a90fb5a652fee0102e1656886e789f983c1c --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/connectcampaignsv2/2024-04-23/service-2.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ccd8ec00bc9897494eb51219532bf8c8bd788c62df5d5bdc35f466254df5a50d +size 9361 diff --git a/py311/lib/python3.11/site-packages/botocore/data/controltower/2018-05-10/endpoint-rule-set-1.json.gz b/py311/lib/python3.11/site-packages/botocore/data/controltower/2018-05-10/endpoint-rule-set-1.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..b42b08fb48f6f2b9bfec73cfdabdbfa0699f00d5 --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/controltower/2018-05-10/endpoint-rule-set-1.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:29cefb05048ecd0d21f84dfeab44b4a66f9a70b3e4d61f8fdec5587140d31e09 +size 1151 diff --git a/py311/lib/python3.11/site-packages/botocore/data/controltower/2018-05-10/service-2.json.gz b/py311/lib/python3.11/site-packages/botocore/data/controltower/2018-05-10/service-2.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..eaf797030f3494e92a7f732edc2d8b2633bb76c4 --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/controltower/2018-05-10/service-2.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:710e686320e28a9557e2930fed1fd5ddee6e9227574e1bf64f27d3479b147904 +size 13991 diff --git a/py311/lib/python3.11/site-packages/botocore/data/customer-profiles/2020-08-15/endpoint-rule-set-1.json.gz b/py311/lib/python3.11/site-packages/botocore/data/customer-profiles/2020-08-15/endpoint-rule-set-1.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..473a7f920da8ae18819061a71c9fdea547ee1d48 --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/customer-profiles/2020-08-15/endpoint-rule-set-1.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9056a72bd3be399b91cdf870e7948765b7491207da2517a9454e3dde986fa911 +size 1148 diff --git a/py311/lib/python3.11/site-packages/botocore/data/customer-profiles/2020-08-15/service-2.json.gz b/py311/lib/python3.11/site-packages/botocore/data/customer-profiles/2020-08-15/service-2.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..6b796acfd813cb1282607e6638925fe35e4d41f0 --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/customer-profiles/2020-08-15/service-2.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:47b11835afe21c58c0be4ed8a68d9d6be5a0753e390d2915048954ece35b8058 +size 53660 diff --git a/py311/lib/python3.11/site-packages/botocore/data/dax/2017-04-19/endpoint-rule-set-1.json.gz b/py311/lib/python3.11/site-packages/botocore/data/dax/2017-04-19/endpoint-rule-set-1.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..3cb0e435fbe852566b77b00b0ab95efca17b277e --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/dax/2017-04-19/endpoint-rule-set-1.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5f67fe1e2a78a79186a91398d5572e30aed52805c690aeec78f878008029537b +size 1145 diff --git a/py311/lib/python3.11/site-packages/botocore/data/dax/2017-04-19/service-2.json.gz b/py311/lib/python3.11/site-packages/botocore/data/dax/2017-04-19/service-2.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..238d81aa70597ce4a0ddad278ea8e57b01b297be --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/dax/2017-04-19/service-2.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3cf7ce8c5ebdb4f4471ed94aade2bdf90a378e87871308a281dbd6563547b7ce +size 10264 diff --git a/py311/lib/python3.11/site-packages/botocore/data/docdb/2014-10-31/endpoint-rule-set-1.json.gz b/py311/lib/python3.11/site-packages/botocore/data/docdb/2014-10-31/endpoint-rule-set-1.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..fb36aeae0f92475c7365fc6f1efaf2fd55a3b41d --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/docdb/2014-10-31/endpoint-rule-set-1.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fb268cb1408be9e99b5427fbc702089228187d47e1e6044973a473e1fa4ae63c +size 1230 diff --git a/py311/lib/python3.11/site-packages/botocore/data/docdb/2014-10-31/service-2.json.gz b/py311/lib/python3.11/site-packages/botocore/data/docdb/2014-10-31/service-2.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..d8c4c6ece599deeeedd6589955f623b821e14d5d --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/docdb/2014-10-31/service-2.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e37cd67e0a1aea39eee1abee0b990f7c1a83919fc813321edcc25527ad27a127 +size 35568 diff --git a/py311/lib/python3.11/site-packages/botocore/data/dsql/2018-05-10/endpoint-rule-set-1.json.gz b/py311/lib/python3.11/site-packages/botocore/data/dsql/2018-05-10/endpoint-rule-set-1.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..2b5726155444f2725801082f7a688221a5d20d4d --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/dsql/2018-05-10/endpoint-rule-set-1.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9c076192c13fee24eaf0faee9f1bbe33178b2baacf91b3f953e0b6fac916e4d9 +size 831 diff --git a/py311/lib/python3.11/site-packages/botocore/data/dsql/2018-05-10/service-2.json.gz b/py311/lib/python3.11/site-packages/botocore/data/dsql/2018-05-10/service-2.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..df12d44060cc90f58feb0b22cc1e612405750b63 --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/dsql/2018-05-10/service-2.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8152dbb56b9aaa6bab9d655f531e3c6ecad3d4cf9586128cd0a3b8cdc9f9b943 +size 6436 diff --git a/py311/lib/python3.11/site-packages/botocore/data/dynamodb/2011-12-05/endpoint-rule-set-1.json.gz b/py311/lib/python3.11/site-packages/botocore/data/dynamodb/2011-12-05/endpoint-rule-set-1.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..eadfd8cc94a1cea507b400c0e89a5348a2adef0f --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/dynamodb/2011-12-05/endpoint-rule-set-1.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:235df03263375a56dac9b6a85383ac5753cb4cd1fd184214efb09927800f9f8d +size 1343 diff --git a/py311/lib/python3.11/site-packages/botocore/data/dynamodb/2012-08-10/endpoint-rule-set-1.json.gz b/py311/lib/python3.11/site-packages/botocore/data/dynamodb/2012-08-10/endpoint-rule-set-1.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..2d27c2214c84dba16f60d61e8efc22e4d16eff3e --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/dynamodb/2012-08-10/endpoint-rule-set-1.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6abb91470ef9c38bb8f3bc1edc6dce94013729bb3b1ab3a200138634640b4350 +size 3447 diff --git a/py311/lib/python3.11/site-packages/botocore/data/dynamodb/2012-08-10/service-2.json.gz b/py311/lib/python3.11/site-packages/botocore/data/dynamodb/2012-08-10/service-2.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..ac3bdff1346934a534cca63a1bd848e23a39e7e6 --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/dynamodb/2012-08-10/service-2.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:badffe39dd64c85b11e3c5ab4df31b169b18c33fb1c862459a930bd0f2966a31 +size 83205 diff --git a/py311/lib/python3.11/site-packages/botocore/data/eks/2017-11-01/endpoint-rule-set-1.json.gz b/py311/lib/python3.11/site-packages/botocore/data/eks/2017-11-01/endpoint-rule-set-1.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..8953f92687d9875e898f292047ea9ba07755e983 --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/eks/2017-11-01/endpoint-rule-set-1.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:61e17184593cc8dc06d2ffba7bdccd62663206c626bf204c5c2d03b46e973954 +size 1265 diff --git a/py311/lib/python3.11/site-packages/botocore/data/eks/2017-11-01/service-2.json.gz b/py311/lib/python3.11/site-packages/botocore/data/eks/2017-11-01/service-2.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..b66f1d336a61482f13f08017ffff9c7f28f97185 --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/eks/2017-11-01/service-2.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:67743982a72722db5edeb69282c3430090c350cb551e3deecdc425e3b32dddee +size 56062 diff --git a/py311/lib/python3.11/site-packages/botocore/data/elbv2/2015-12-01/endpoint-rule-set-1.json.gz b/py311/lib/python3.11/site-packages/botocore/data/elbv2/2015-12-01/endpoint-rule-set-1.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..042d3b533d7138c3a250aacb4227cf0b62ab8857 --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/elbv2/2015-12-01/endpoint-rule-set-1.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:08ae613856a7270902c471b71b114705776c206fef9435b2255abfdc55de6393 +size 1242 diff --git a/py311/lib/python3.11/site-packages/botocore/data/elbv2/2015-12-01/service-2.json.gz b/py311/lib/python3.11/site-packages/botocore/data/elbv2/2015-12-01/service-2.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..e47289042b00d4431dc5aa8a3afaf784a4e7d31e --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/elbv2/2015-12-01/service-2.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e9bd23cc2ac9007a5f4d5309057756f444f0ffd77f3713031472135933a8f6d8 +size 32908 diff --git a/py311/lib/python3.11/site-packages/botocore/data/emr/2009-03-31/endpoint-rule-set-1.json.gz b/py311/lib/python3.11/site-packages/botocore/data/emr/2009-03-31/endpoint-rule-set-1.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..d040cfdac9a584376ddbb1657f545518cf0e93e7 --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/emr/2009-03-31/endpoint-rule-set-1.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f1130b8c894cab8b619ef6887bb34d5fb99e0c3d3527a892121278069d32d6d8 +size 1239 diff --git a/py311/lib/python3.11/site-packages/botocore/data/emr/2009-03-31/service-2.json.gz b/py311/lib/python3.11/site-packages/botocore/data/emr/2009-03-31/service-2.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..97ac04fe6a532c8ce521798b6218ca0884dda323 --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/emr/2009-03-31/service-2.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:86adad5412f267456b63694f10f1ebdd5edaff4ecf0c71bad4bd634246b3d06e +size 47420 diff --git a/py311/lib/python3.11/site-packages/botocore/data/events/2014-02-03/endpoint-rule-set-1.json.gz b/py311/lib/python3.11/site-packages/botocore/data/events/2014-02-03/endpoint-rule-set-1.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..418cfd510de58053f11f5f14ecf18fb64519f1b1 --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/events/2014-02-03/endpoint-rule-set-1.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ead25696b13b59b253bdfc995bee57cc77b220bd99399c845d2d0ad4865d58a2 +size 1856 diff --git a/py311/lib/python3.11/site-packages/botocore/data/events/2014-02-03/service-2.json.gz b/py311/lib/python3.11/site-packages/botocore/data/events/2014-02-03/service-2.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..3d44e00af3d80bce429bf72ffff5b236005ebba8 --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/events/2014-02-03/service-2.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:42789f6ecf2d0db05a0dbcd01bec30d53fa5761796089a56299379a3edcfd8bf +size 5254 diff --git a/py311/lib/python3.11/site-packages/botocore/data/events/2015-10-07/endpoint-rule-set-1.json.gz b/py311/lib/python3.11/site-packages/botocore/data/events/2015-10-07/endpoint-rule-set-1.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..371c0640a4eb5b13294c8fa2a94724ebb670e75c --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/events/2015-10-07/endpoint-rule-set-1.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d4ec9f8048a229e022457c5a10b51299241b271e4ab8bc8a9e54adcfa7f0cce8 +size 1843 diff --git a/py311/lib/python3.11/site-packages/botocore/data/events/2015-10-07/service-2.json.gz b/py311/lib/python3.11/site-packages/botocore/data/events/2015-10-07/service-2.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..ea7ae01ef16dd33d510eeb695ae84aaef7430671 --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/events/2015-10-07/service-2.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f9a03de4c2f32fecac112ec721a80de8c218d1cd4cf4eb7fd16c486fda315488 +size 36776 diff --git a/py311/lib/python3.11/site-packages/botocore/data/evidently/2021-02-01/endpoint-rule-set-1.json.gz b/py311/lib/python3.11/site-packages/botocore/data/evidently/2021-02-01/endpoint-rule-set-1.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..022a08560b511a684f63931807ec2f15a48eb3e0 --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/evidently/2021-02-01/endpoint-rule-set-1.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4a63095cac45e845d1db5d343f9b79500fe17503ca165968f076d71d1aa4174a +size 1150 diff --git a/py311/lib/python3.11/site-packages/botocore/data/evidently/2021-02-01/service-2.json.gz b/py311/lib/python3.11/site-packages/botocore/data/evidently/2021-02-01/service-2.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..37e650c848dc67f0d4acbe96d09ce914c09057d9 --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/evidently/2021-02-01/service-2.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0850cfcfb0ac8a3a0ab4d54ba5d9f4d37a1c6c494e5db67d931a5ef84e917e03 +size 20415 diff --git a/py311/lib/python3.11/site-packages/botocore/data/evs/2023-07-27/endpoint-rule-set-1.json.gz b/py311/lib/python3.11/site-packages/botocore/data/evs/2023-07-27/endpoint-rule-set-1.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..9aff40b75d346b0eaea46599936cf24074a4217a --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/evs/2023-07-27/endpoint-rule-set-1.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:96de0659a48d8c5edeb3d871210e3cff7db93e2bf4f6c12c5486969a2755cbe5 +size 1295 diff --git a/py311/lib/python3.11/site-packages/botocore/data/evs/2023-07-27/service-2.json.gz b/py311/lib/python3.11/site-packages/botocore/data/evs/2023-07-27/service-2.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..0fb3a1d2e474f06a156d38c9833ac6a18bf2fe61 --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/evs/2023-07-27/service-2.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2fb16145cd342750cbcd67bdf72b9deaaecce2d45b70ccf3e1960bafa843ff8f +size 10841 diff --git a/py311/lib/python3.11/site-packages/botocore/data/finspace/2021-03-12/endpoint-rule-set-1.json.gz b/py311/lib/python3.11/site-packages/botocore/data/finspace/2021-03-12/endpoint-rule-set-1.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..3283f3b55d152068de086b0ecd4bd245dd476591 --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/finspace/2021-03-12/endpoint-rule-set-1.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e9c8befccf09e619450e45c4affbf9c2a1474cdf81479928ed9bda0235ef7569 +size 1149 diff --git a/py311/lib/python3.11/site-packages/botocore/data/finspace/2021-03-12/service-2.json.gz b/py311/lib/python3.11/site-packages/botocore/data/finspace/2021-03-12/service-2.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..7bfd5db2dad3452e7c6b56a7c3f133b63226f50a --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/finspace/2021-03-12/service-2.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e866011718e60e7c7109b7394dac655b7ac7756a87bc8e8b55adf7a830e8d6fa +size 30464 diff --git a/py311/lib/python3.11/site-packages/botocore/data/fms/2018-01-01/endpoint-rule-set-1.json.gz b/py311/lib/python3.11/site-packages/botocore/data/fms/2018-01-01/endpoint-rule-set-1.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..c7c5cda4f885453a4fce6ec974c1e1a5670c3acc --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/fms/2018-01-01/endpoint-rule-set-1.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9a447743066561ca7c2e9b10ecc8bda78b53d794ad894583771c46e71c7aaff9 +size 1145 diff --git a/py311/lib/python3.11/site-packages/botocore/data/fms/2018-01-01/service-2.json.gz b/py311/lib/python3.11/site-packages/botocore/data/fms/2018-01-01/service-2.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..77830be2be9ed77f9b82e8b232750f1f8fbf10b7 --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/fms/2018-01-01/service-2.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cb43e77f9c1afef671a8566734cb9cbf402b1ca54e5498b04d4c639be0de8ba7 +size 34811 diff --git a/py311/lib/python3.11/site-packages/botocore/data/forecast/2018-06-26/endpoint-rule-set-1.json.gz b/py311/lib/python3.11/site-packages/botocore/data/forecast/2018-06-26/endpoint-rule-set-1.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..b33c6d0c98e72f36e7b2d6097b3e1715953927bd --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/forecast/2018-06-26/endpoint-rule-set-1.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8caf75124a9805143b1690cbb09597e31c1963ebbdf67e953621de344a8028bb +size 1148 diff --git a/py311/lib/python3.11/site-packages/botocore/data/forecast/2018-06-26/service-2.json.gz b/py311/lib/python3.11/site-packages/botocore/data/forecast/2018-06-26/service-2.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..c36cb8249f512e9c335c5b59fc2c984003aa48b1 --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/forecast/2018-06-26/service-2.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:36dc5715f0a5ca56ddb58cb9c31a7662476c99e5ddbf99fb08ef388e89acad0a +size 40082 diff --git a/py311/lib/python3.11/site-packages/botocore/data/gameliftstreams/2018-05-10/endpoint-rule-set-1.json.gz b/py311/lib/python3.11/site-packages/botocore/data/gameliftstreams/2018-05-10/endpoint-rule-set-1.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..cd55f197b1fdce2d20f4ad65a7e9031b6e15c914 --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/gameliftstreams/2018-05-10/endpoint-rule-set-1.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7b5cf9df83e23bd1927b3a14a84c7a9eeb8218381f0c233f37d4afcd7132c170 +size 839 diff --git a/py311/lib/python3.11/site-packages/botocore/data/gameliftstreams/2018-05-10/service-2.json.gz b/py311/lib/python3.11/site-packages/botocore/data/gameliftstreams/2018-05-10/service-2.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..631a0e63a92a352ad05eb720639b2d38b9d56804 --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/gameliftstreams/2018-05-10/service-2.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:17fc1f2623462255d960ccb74fa15f029507b2547b4fe135fba77f62cebb4092 +size 26747 diff --git a/py311/lib/python3.11/site-packages/botocore/data/glue/2017-03-31/endpoint-rule-set-1.json.gz b/py311/lib/python3.11/site-packages/botocore/data/glue/2017-03-31/endpoint-rule-set-1.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..cbe1ce5fbce0c3c1c5e820dcf4eac52cb719bd05 --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/glue/2017-03-31/endpoint-rule-set-1.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1f39d8de31aa18dba3e2d08e9f6e37851b02c00c8e1968f0bee73e1c5db2a885 +size 1146 diff --git a/py311/lib/python3.11/site-packages/botocore/data/glue/2017-03-31/service-2.json.gz b/py311/lib/python3.11/site-packages/botocore/data/glue/2017-03-31/service-2.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..d31247777cac4bc1eae7698f2ea96bcd4bd77238 --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/glue/2017-03-31/service-2.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9204f726ddaa0f48f91492fdec29e7968c861eeb462eb98bfbc0e39ea5230cd5 +size 170328 diff --git a/py311/lib/python3.11/site-packages/botocore/data/grafana/2020-08-18/endpoint-rule-set-1.json.gz b/py311/lib/python3.11/site-packages/botocore/data/grafana/2020-08-18/endpoint-rule-set-1.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..0e5ec2687d27bfd6732e302f4b604943d0d58f3c --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/grafana/2020-08-18/endpoint-rule-set-1.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:89bb9ef845c95c24ee229641e106a28bd260e0a3cd17f5c982c733355939ff1f +size 1148 diff --git a/py311/lib/python3.11/site-packages/botocore/data/grafana/2020-08-18/service-2.json.gz b/py311/lib/python3.11/site-packages/botocore/data/grafana/2020-08-18/service-2.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..ea52df5d00038bf931adb9791d4556523715a6b5 --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/grafana/2020-08-18/service-2.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5bf9756f7dcdf72c969776bf97651fa216f7374b67f12fb075aaf7096d5c4d5d +size 15023 diff --git a/py311/lib/python3.11/site-packages/botocore/data/greengrassv2/2020-11-30/endpoint-rule-set-1.json.gz b/py311/lib/python3.11/site-packages/botocore/data/greengrassv2/2020-11-30/endpoint-rule-set-1.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..5721618e9aac1a5396f8d01236609615ec8ff42a --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/greengrassv2/2020-11-30/endpoint-rule-set-1.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:853a7b53609f7cb85f0ba87ac138abfe86dffb0967b469654bd427805b2b7524 +size 1361 diff --git a/py311/lib/python3.11/site-packages/botocore/data/greengrassv2/2020-11-30/service-2.json.gz b/py311/lib/python3.11/site-packages/botocore/data/greengrassv2/2020-11-30/service-2.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..bb3d301af1873a87182af491866df917c34f2a3d --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/greengrassv2/2020-11-30/service-2.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:effc537a7da8d88e11924b0f8f3fa8510114a7b3b163c6b660632db90c80a9a9 +size 20272 diff --git a/py311/lib/python3.11/site-packages/botocore/data/iam/2010-05-08/endpoint-rule-set-1.json.gz b/py311/lib/python3.11/site-packages/botocore/data/iam/2010-05-08/endpoint-rule-set-1.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..33a8ababace5d436ded725830a195230e865a403 --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/iam/2010-05-08/endpoint-rule-set-1.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b4f1cec6a6f8eaf6ec7cc01ed8b2fb8619c9adf0c0c79a27b81a04b486df7c66 +size 2238 diff --git a/py311/lib/python3.11/site-packages/botocore/data/iam/2010-05-08/service-2.json.gz b/py311/lib/python3.11/site-packages/botocore/data/iam/2010-05-08/service-2.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..f5230198eee0270f0404516295bc14db686bff1c --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/iam/2010-05-08/service-2.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:52a6572d8da78efda1a8c3fbb93cebe5a8a5592212bf55bd1aab6763c8a6fda1 +size 81064 diff --git a/py311/lib/python3.11/site-packages/botocore/data/imagebuilder/2019-12-02/endpoint-rule-set-1.json.gz b/py311/lib/python3.11/site-packages/botocore/data/imagebuilder/2019-12-02/endpoint-rule-set-1.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..d692262ac87b237633926ec82ef12a567808d8a6 --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/imagebuilder/2019-12-02/endpoint-rule-set-1.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ba1a8894db65a780895f504740a2dd8aa8bde924edccc54fd9f0f38a3aec128d +size 1237 diff --git a/py311/lib/python3.11/site-packages/botocore/data/imagebuilder/2019-12-02/service-2.json.gz b/py311/lib/python3.11/site-packages/botocore/data/imagebuilder/2019-12-02/service-2.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..7e67f7d9cb5fd7b19406406dcdc82f767c6d4fff --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/imagebuilder/2019-12-02/service-2.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:210ad4e4937d1c226c21ff3a474ade44297dd8a50bae1a83bd68c06fa43563a9 +size 44248 diff --git a/py311/lib/python3.11/site-packages/botocore/data/importexport/2010-06-01/endpoint-rule-set-1.json.gz b/py311/lib/python3.11/site-packages/botocore/data/importexport/2010-06-01/endpoint-rule-set-1.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..3808025c25574850b01cf7bd373dddaba490c6bd --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/importexport/2010-06-01/endpoint-rule-set-1.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a6e21b46ff7e61300328fb84038435b7e02fd15463524d2d500d3366eba214d4 +size 1599 diff --git a/py311/lib/python3.11/site-packages/botocore/data/importexport/2010-06-01/service-2.json.gz b/py311/lib/python3.11/site-packages/botocore/data/importexport/2010-06-01/service-2.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..8801a3644cb30adf62128b93be7ceb83f39f2e5c --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/importexport/2010-06-01/service-2.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3b124f9ec8b8e3b91ee799f05334fb14b79373888fa4618e55a496179192bcd2 +size 4733 diff --git a/py311/lib/python3.11/site-packages/botocore/data/iotevents/2018-07-27/endpoint-rule-set-1.json.gz b/py311/lib/python3.11/site-packages/botocore/data/iotevents/2018-07-27/endpoint-rule-set-1.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..11048a57aec8f1ff618b9db76b68469aadb0c898 --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/iotevents/2018-07-27/endpoint-rule-set-1.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ab371f2b254b5515d407e0df91b918c31cd7377d223240015f06b1dd333038a4 +size 1149 diff --git a/py311/lib/python3.11/site-packages/botocore/data/iotevents/2018-07-27/service-2.json.gz b/py311/lib/python3.11/site-packages/botocore/data/iotevents/2018-07-27/service-2.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..8b71872a4f7078984f686611d92211a1a67c1351 --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/iotevents/2018-07-27/service-2.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ea57e2be38a7f3a2f59b626a4c0796c95822aa6f6358b855d6284657681a5ee9 +size 16112 diff --git a/py311/lib/python3.11/site-packages/botocore/data/iottwinmaker/2021-11-29/endpoint-rule-set-1.json.gz b/py311/lib/python3.11/site-packages/botocore/data/iottwinmaker/2021-11-29/endpoint-rule-set-1.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..5a8980c0a6bf3f45ea4b5027576fc2def80aaa08 --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/iottwinmaker/2021-11-29/endpoint-rule-set-1.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6da2f0b104325b30af00ee1b5fa88194904b4f650a35e7f80bb30ca83cbfec20 +size 1152 diff --git a/py311/lib/python3.11/site-packages/botocore/data/iottwinmaker/2021-11-29/service-2.json.gz b/py311/lib/python3.11/site-packages/botocore/data/iottwinmaker/2021-11-29/service-2.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..63fea899d1b6e4d3204cae917230298d9bc94aba --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/iottwinmaker/2021-11-29/service-2.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ea302a530d000cde882fc0cd1b56bdc16cb5d9f8217a19344cf73b988d364d3d +size 16782 diff --git a/py311/lib/python3.11/site-packages/botocore/data/ivschat/2020-07-14/endpoint-rule-set-1.json.gz b/py311/lib/python3.11/site-packages/botocore/data/ivschat/2020-07-14/endpoint-rule-set-1.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..e64799fe7538f61679a388964afce224619dd064 --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/ivschat/2020-07-14/endpoint-rule-set-1.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b4753b5d8afdbca4ef78ecf615fc8c7bc8a7cc4c5a47866f58a431e54c99a94c +size 1149 diff --git a/py311/lib/python3.11/site-packages/botocore/data/ivschat/2020-07-14/service-2.json.gz b/py311/lib/python3.11/site-packages/botocore/data/ivschat/2020-07-14/service-2.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..62211fbdacd6244b0340a6a29b4ba536ce07c27d --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/ivschat/2020-07-14/service-2.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e490ecc3306ace03813335aab8765be08cccf432a49402505f2b81c9e85fd45b +size 8309 diff --git a/py311/lib/python3.11/site-packages/botocore/data/kafka/2018-11-14/endpoint-rule-set-1.json.gz b/py311/lib/python3.11/site-packages/botocore/data/kafka/2018-11-14/endpoint-rule-set-1.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..0f37b3af8a60e86214f580f6e525920c352fd761 --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/kafka/2018-11-14/endpoint-rule-set-1.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c2bc28a9ec8c87e799cb61695e902ffa46545f7ed4f21e95382a7b9c964a8505 +size 1232 diff --git a/py311/lib/python3.11/site-packages/botocore/data/kafka/2018-11-14/service-2.json.gz b/py311/lib/python3.11/site-packages/botocore/data/kafka/2018-11-14/service-2.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..131a2f0f0b9b1c6c09da15f906c7f4f39716d89c --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/kafka/2018-11-14/service-2.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ba02c6f159ee03e8489bd9a271ebeabd142df66a80831c74a19fc9bbaf5d3cc8 +size 22702 diff --git a/py311/lib/python3.11/site-packages/botocore/data/kinesis-video-webrtc-storage/2018-05-10/endpoint-rule-set-1.json.gz b/py311/lib/python3.11/site-packages/botocore/data/kinesis-video-webrtc-storage/2018-05-10/endpoint-rule-set-1.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..aa878fc0995b5c9bfc25fc1cca0d748476c25486 --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/kinesis-video-webrtc-storage/2018-05-10/endpoint-rule-set-1.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0d5e41daf270f61dc212c52171f4d988fdf98d51b717fb181dd5527e6e2867e9 +size 1302 diff --git a/py311/lib/python3.11/site-packages/botocore/data/kinesis-video-webrtc-storage/2018-05-10/service-2.json.gz b/py311/lib/python3.11/site-packages/botocore/data/kinesis-video-webrtc-storage/2018-05-10/service-2.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..6d883349250e9b9aa2c10e46c7e464c24c6454b1 --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/kinesis-video-webrtc-storage/2018-05-10/service-2.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:193161763de323db9fdcd7162587e5d56e42215b48302488efab6bd6584fa41b +size 2094 diff --git a/py311/lib/python3.11/site-packages/botocore/data/launch-wizard/2018-05-10/endpoint-rule-set-1.json.gz b/py311/lib/python3.11/site-packages/botocore/data/launch-wizard/2018-05-10/endpoint-rule-set-1.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..058434fa7e459a717e0c1061fb158cdec76bb09f --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/launch-wizard/2018-05-10/endpoint-rule-set-1.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fe40783977d80ab9e8d6690f153543d9655f1792a764faddbce502f1a2f5786e +size 1302 diff --git a/py311/lib/python3.11/site-packages/botocore/data/launch-wizard/2018-05-10/service-2.json.gz b/py311/lib/python3.11/site-packages/botocore/data/launch-wizard/2018-05-10/service-2.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..6f229198c7b0bfd0c077d7c0660f79346d73ea79 --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/launch-wizard/2018-05-10/service-2.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e471b4d8df4456f6ebbcef61514cd2800a64ee6ec557453e8c3a528039475902 +size 4452 diff --git a/py311/lib/python3.11/site-packages/botocore/data/location/2020-11-19/endpoint-rule-set-1.json.gz b/py311/lib/python3.11/site-packages/botocore/data/location/2020-11-19/endpoint-rule-set-1.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..a73597c06cd70fdec3b0e3223ecd62567750fc4b --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/location/2020-11-19/endpoint-rule-set-1.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:29defc9e7a4a8c5d245005d06bbacd55595ef41d14bdaa2406a0654ff126d21a +size 1145 diff --git a/py311/lib/python3.11/site-packages/botocore/data/location/2020-11-19/service-2.json.gz b/py311/lib/python3.11/site-packages/botocore/data/location/2020-11-19/service-2.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..6f79aabd5dd7d01af5e7b43fbe2b0eb9d0e868ee --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/location/2020-11-19/service-2.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:739b84940fa6a8873eb2888bfb2d443d9dfc97fd65668a720ad9b99bf63572f7 +size 44276 diff --git a/py311/lib/python3.11/site-packages/botocore/data/macie2/2020-01-01/endpoint-rule-set-1.json.gz b/py311/lib/python3.11/site-packages/botocore/data/macie2/2020-01-01/endpoint-rule-set-1.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..f752637fb4db86ed749a6d7bc9c6f05786ffeb9d --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/macie2/2020-01-01/endpoint-rule-set-1.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b6fb598d03c698a1138d3f99b5008d6c5c17cf5b4c8cff204c835399c86f8e8d +size 1148 diff --git a/py311/lib/python3.11/site-packages/botocore/data/macie2/2020-01-01/service-2.json.gz b/py311/lib/python3.11/site-packages/botocore/data/macie2/2020-01-01/service-2.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..1ce844919c93df1dbaca6270c29843916e891a49 --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/macie2/2020-01-01/service-2.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2cfe64688071fad66dd663d2cae41c755134e73182909d78e346678e1c334cd9 +size 59305 diff --git a/py311/lib/python3.11/site-packages/botocore/data/mailmanager/2023-10-17/endpoint-rule-set-1.json.gz b/py311/lib/python3.11/site-packages/botocore/data/mailmanager/2023-10-17/endpoint-rule-set-1.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..f0f58b6b847f208bfd29045078c92fbd5f6ef736 --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/mailmanager/2023-10-17/endpoint-rule-set-1.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cd81297dcb2cd5d736a4de14469f311e039dbf8b840c5cd21ce2e227d0f4fc7c +size 1302 diff --git a/py311/lib/python3.11/site-packages/botocore/data/mailmanager/2023-10-17/service-2.json.gz b/py311/lib/python3.11/site-packages/botocore/data/mailmanager/2023-10-17/service-2.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..e23e88ac750661910e359e9a23564e3f3c11eca2 --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/mailmanager/2023-10-17/service-2.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2bd96a914f53aecabdad0ed0454897b896c16fa960cbaac4276e2bc05e29a61d +size 22050 diff --git a/py311/lib/python3.11/site-packages/botocore/data/marketplace-reporting/2018-05-10/endpoint-rule-set-1.json.gz b/py311/lib/python3.11/site-packages/botocore/data/marketplace-reporting/2018-05-10/endpoint-rule-set-1.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..ad1586f6bbc4a4f643b10330b60945ee372eda6f --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/marketplace-reporting/2018-05-10/endpoint-rule-set-1.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d1b826f123bc100f6e970155d6744580b4883699158dfca66833c5cea761f7c3 +size 1308 diff --git a/py311/lib/python3.11/site-packages/botocore/data/marketplace-reporting/2018-05-10/service-2.json.gz b/py311/lib/python3.11/site-packages/botocore/data/marketplace-reporting/2018-05-10/service-2.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..8f4b9dac9e40bbb14abbb393554d5a70136389f7 --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/marketplace-reporting/2018-05-10/service-2.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f4c0a1cfc410481358094b92bb518213da2f1ea9a042ec32e2f8b01c1c821032 +size 2451 diff --git a/py311/lib/python3.11/site-packages/botocore/data/mturk/2017-01-17/endpoint-rule-set-1.json.gz b/py311/lib/python3.11/site-packages/botocore/data/mturk/2017-01-17/endpoint-rule-set-1.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..7113339a367dc9dd89bb3f5f0e1b6aa2dfa69876 --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/mturk/2017-01-17/endpoint-rule-set-1.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e4fdcb9db0eda38f334b6475889d458ddc34f130ddf946ce743175a0ebcfc331 +size 1217 diff --git a/py311/lib/python3.11/site-packages/botocore/data/mturk/2017-01-17/service-2.json.gz b/py311/lib/python3.11/site-packages/botocore/data/mturk/2017-01-17/service-2.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..9362655f435990868c0aabd5d7cb98e657f0960d --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/mturk/2017-01-17/service-2.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6a707a5334db73011a6c72bce29e5a11111bf48cbd398cd8872a2ea6fb5c463b +size 19770 diff --git a/py311/lib/python3.11/site-packages/botocore/data/oam/2022-06-10/endpoint-rule-set-1.json.gz b/py311/lib/python3.11/site-packages/botocore/data/oam/2022-06-10/endpoint-rule-set-1.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..df68c4dc6fc53675f8637594e08b4fedb24384eb --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/oam/2022-06-10/endpoint-rule-set-1.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:501a6e50974c9ebf09ec8e12da005fc06288045f79bb3c3979f569d6c3a6ec0b +size 1295 diff --git a/py311/lib/python3.11/site-packages/botocore/data/oam/2022-06-10/service-2.json.gz b/py311/lib/python3.11/site-packages/botocore/data/oam/2022-06-10/service-2.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..43564f66a34ab6c2c5ae97f422771765e4c3d5c0 --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/oam/2022-06-10/service-2.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:16e922ca37a7f471acf6070f2653236dde7fe3010523b2ee2cc1ce67a892a68c +size 7039 diff --git a/py311/lib/python3.11/site-packages/botocore/data/organizations/2016-11-28/endpoint-rule-set-1.json.gz b/py311/lib/python3.11/site-packages/botocore/data/organizations/2016-11-28/endpoint-rule-set-1.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..12c25270a60cef2a574209b03417b4656c2f2e8d --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/organizations/2016-11-28/endpoint-rule-set-1.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0a9710de1a8a1219e0fff5986490da72009cd3f7a42a10eaa14d6e7013caf5d6 +size 1651 diff --git a/py311/lib/python3.11/site-packages/botocore/data/organizations/2016-11-28/service-2.json.gz b/py311/lib/python3.11/site-packages/botocore/data/organizations/2016-11-28/service-2.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..633b6ede9049bfebe175ef68256597ca2601ef4f --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/organizations/2016-11-28/service-2.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:43ea98ee7c75e10280f6d30f7709233905cf944727453756a37660150430cd47 +size 39484 diff --git a/py311/lib/python3.11/site-packages/botocore/data/panorama/2019-07-24/service-2.json.gz b/py311/lib/python3.11/site-packages/botocore/data/panorama/2019-07-24/service-2.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..635105b110dc94e88d22a5b35f31699aa9d37897 --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/panorama/2019-07-24/service-2.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:41b71043e5fcd5e5606a20ce1f15d5a5fbb2cbba7636f08bf06a100283a34906 +size 11986 diff --git a/py311/lib/python3.11/site-packages/botocore/data/partnercentral-channel/2024-03-18/endpoint-rule-set-1.json.gz b/py311/lib/python3.11/site-packages/botocore/data/partnercentral-channel/2024-03-18/endpoint-rule-set-1.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..cb1e0692fd7f232ccaac51df954d02a8e68e53fc --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/partnercentral-channel/2024-03-18/endpoint-rule-set-1.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4269a564bd8fde5b12b1932e93961ce314734b4c72903282a3ecff3fe799b12a +size 1121 diff --git a/py311/lib/python3.11/site-packages/botocore/data/partnercentral-channel/2024-03-18/service-2.json.gz b/py311/lib/python3.11/site-packages/botocore/data/partnercentral-channel/2024-03-18/service-2.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..9ab3c91c7186bf59e8c39e685c15f6e97374c8f6 --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/partnercentral-channel/2024-03-18/service-2.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:934a6f2b76e5ce79f9d7d34e628a15cd0bd0fcaa70f3811730d8dd194863480b +size 6726 diff --git a/py311/lib/python3.11/site-packages/botocore/data/personalize-events/2018-03-22/endpoint-rule-set-1.json.gz b/py311/lib/python3.11/site-packages/botocore/data/personalize-events/2018-03-22/endpoint-rule-set-1.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..8b945b4071595670785d52fcec19b85bc689c4df --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/personalize-events/2018-03-22/endpoint-rule-set-1.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:233ad86644c45523394663b2603f77d87f1bbb95438ceda50d2da70a2664011a +size 1158 diff --git a/py311/lib/python3.11/site-packages/botocore/data/personalize-events/2018-03-22/service-2.json.gz b/py311/lib/python3.11/site-packages/botocore/data/personalize-events/2018-03-22/service-2.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..ec4546efd5e5894dbe5c07a5d26c220ce5761d1b --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/personalize-events/2018-03-22/service-2.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:335753113801a5e6b5429b5cf7b24ffe03fea6bcd99d910fb3eb2065a4fda4d7 +size 3891 diff --git a/py311/lib/python3.11/site-packages/botocore/data/personalize/2018-05-22/endpoint-rule-set-1.json.gz b/py311/lib/python3.11/site-packages/botocore/data/personalize/2018-05-22/endpoint-rule-set-1.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..5ea517b1a24b12de368d64c7067fc6fb492f0480 --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/personalize/2018-05-22/endpoint-rule-set-1.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:22570ea3319fd49ff430f561c50e4e6f2c390650db25d8448aba15d4a9306bf2 +size 1153 diff --git a/py311/lib/python3.11/site-packages/botocore/data/personalize/2018-05-22/service-2.json.gz b/py311/lib/python3.11/site-packages/botocore/data/personalize/2018-05-22/service-2.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..002dd079753e8f92454743dd904a2e196aab8c18 --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/personalize/2018-05-22/service-2.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0d8a4a6a7e0e227a74578420b111a72040703640864fd2f2f40e4d9b5e85b464 +size 31275 diff --git a/py311/lib/python3.11/site-packages/botocore/data/pipes/2015-10-07/endpoint-rule-set-1.json.gz b/py311/lib/python3.11/site-packages/botocore/data/pipes/2015-10-07/endpoint-rule-set-1.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..4d09ddce9cfe5ed343b57a30d9ea4beb1fb5e835 --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/pipes/2015-10-07/endpoint-rule-set-1.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d93872c995dc6dc5dadc8b7738cfad6298372245324e3f5c86feec0fce9d9bb0 +size 1293 diff --git a/py311/lib/python3.11/site-packages/botocore/data/pipes/2015-10-07/service-2.json.gz b/py311/lib/python3.11/site-packages/botocore/data/pipes/2015-10-07/service-2.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..8789f091ae8dce3e63e487050e58e74d0e2b1d30 --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/pipes/2015-10-07/service-2.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4fce5e5ca4ff9b9eb21c9ddcfdbfcc7e25a73a7693c6794a5d02aeb636cf9762 +size 23040 diff --git a/py311/lib/python3.11/site-packages/botocore/data/pricing/2017-10-15/endpoint-rule-set-1.json.gz b/py311/lib/python3.11/site-packages/botocore/data/pricing/2017-10-15/endpoint-rule-set-1.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..b69517860b570d747a8bbb3e0b0b82497c1d3779 --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/pricing/2017-10-15/endpoint-rule-set-1.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:961c8ede8013c32215ec2829c7c5c5f12a5c65279e70119544d33e20f750f4af +size 1217 diff --git a/py311/lib/python3.11/site-packages/botocore/data/pricing/2017-10-15/service-2.json.gz b/py311/lib/python3.11/site-packages/botocore/data/pricing/2017-10-15/service-2.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..ec8f493416b95884cc92197951bc9a349ff276db --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/pricing/2017-10-15/service-2.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c5c882eb612271cc0b5fba6b2ed6b1fbcaf1aa08d7950811be08d653ba99a088 +size 4542 diff --git a/py311/lib/python3.11/site-packages/botocore/data/qapps/2023-11-27/endpoint-rule-set-1.json.gz b/py311/lib/python3.11/site-packages/botocore/data/qapps/2023-11-27/endpoint-rule-set-1.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..9f0ef14909b2848e02b6b0e7b332e8a896468151 --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/qapps/2023-11-27/endpoint-rule-set-1.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:af3eff16bb7896b9cdd46104b6f7c7041b6e41985728c4e001c4a58b9813d474 +size 1300 diff --git a/py311/lib/python3.11/site-packages/botocore/data/qapps/2023-11-27/service-2.json.gz b/py311/lib/python3.11/site-packages/botocore/data/qapps/2023-11-27/service-2.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..191ce431691c357821f9e5536c524e555d7a484d --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/qapps/2023-11-27/service-2.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ca5b1a701c30a7ae4c7c4c7bc810e91857c51f59125ad2df884a470bfdb8dc8f +size 13950 diff --git a/py311/lib/python3.11/site-packages/botocore/data/ram/2018-01-04/endpoint-rule-set-1.json.gz b/py311/lib/python3.11/site-packages/botocore/data/ram/2018-01-04/endpoint-rule-set-1.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..a738474c426b55b45bfc18a4a5613d0be00beb6c --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/ram/2018-01-04/endpoint-rule-set-1.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e85ac13b9b87311d41abf3345cb0a28a75a1936ffecb4f6e2fa43f4e5dd5bcab +size 1230 diff --git a/py311/lib/python3.11/site-packages/botocore/data/ram/2018-01-04/service-2.json.gz b/py311/lib/python3.11/site-packages/botocore/data/ram/2018-01-04/service-2.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..927ad18ed00cd94bf06be48873efa07a44f579c9 --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/ram/2018-01-04/service-2.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b76690d3bdc2b004cc30360b20e01289eb813ec0d3e51d7b0538f2a21afda646 +size 18021 diff --git a/py311/lib/python3.11/site-packages/botocore/data/redshift-data/2019-12-20/endpoint-rule-set-1.json.gz b/py311/lib/python3.11/site-packages/botocore/data/redshift-data/2019-12-20/endpoint-rule-set-1.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..832a07da32a37ecbc9c5cbdb89e64c1a64196ae9 --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/redshift-data/2019-12-20/endpoint-rule-set-1.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6c4a11ff6f98ce454fb43dbf0e64b9c8e1b591ad220abd170f33aeb50eb6f8e6 +size 1152 diff --git a/py311/lib/python3.11/site-packages/botocore/data/redshift-data/2019-12-20/service-2.json.gz b/py311/lib/python3.11/site-packages/botocore/data/redshift-data/2019-12-20/service-2.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..6ae76d2acfa968a8a42616a04621230d743230d4 --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/redshift-data/2019-12-20/service-2.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a43bb7c15d43fb2acaa95b6a6482e8241d95b1de7819582a81ae6cb338b4d7c9 +size 8044 diff --git a/py311/lib/python3.11/site-packages/botocore/data/redshift-serverless/2021-04-21/endpoint-rule-set-1.json.gz b/py311/lib/python3.11/site-packages/botocore/data/redshift-serverless/2021-04-21/endpoint-rule-set-1.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..6d38f33a86d501893c6f106f9e5b164e858bf63e --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/redshift-serverless/2021-04-21/endpoint-rule-set-1.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:68213c6ece38ab0730c7aac15c76bf39b346c3e27196eb37f3299972e9a8aa1b +size 1156 diff --git a/py311/lib/python3.11/site-packages/botocore/data/redshift-serverless/2021-04-21/service-2.json.gz b/py311/lib/python3.11/site-packages/botocore/data/redshift-serverless/2021-04-21/service-2.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..fffb9dfda940e3a271acf0e2e2ccdfee2ec04444 --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/redshift-serverless/2021-04-21/service-2.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ee2b65b131ef88494c1f3fc578777e0a3bcc4f0de9409e748bb0d6e94c32e872 +size 22461 diff --git a/py311/lib/python3.11/site-packages/botocore/data/resource-groups/2017-11-27/endpoint-rule-set-1.json.gz b/py311/lib/python3.11/site-packages/botocore/data/resource-groups/2017-11-27/endpoint-rule-set-1.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..d6b24a8247772d7c68110204647706517260bb2b --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/resource-groups/2017-11-27/endpoint-rule-set-1.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a51d6e57bc5691b14bd25aaf3293e22bdbfa144afb6f80f9911f8959b15b03a3 +size 1239 diff --git a/py311/lib/python3.11/site-packages/botocore/data/resource-groups/2017-11-27/service-2.json.gz b/py311/lib/python3.11/site-packages/botocore/data/resource-groups/2017-11-27/service-2.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..620250d30c4fe9a5bfe7656e32d767460d6cb6ea --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/resource-groups/2017-11-27/service-2.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c277097c52c3ea1e0d18a04994f833e259f295e16d7e6d1aabe7ae209a4d777b +size 14326 diff --git a/py311/lib/python3.11/site-packages/botocore/data/route53globalresolver/2022-09-27/endpoint-rule-set-1.json.gz b/py311/lib/python3.11/site-packages/botocore/data/route53globalresolver/2022-09-27/endpoint-rule-set-1.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..4227dd982446bb6149e5ca076d0d74a2bb5fb984 --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/route53globalresolver/2022-09-27/endpoint-rule-set-1.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5a4612526df9e38dc0e17bdc85751e9954d66f6dbc7553b1d5503f5502d583ef +size 845 diff --git a/py311/lib/python3.11/site-packages/botocore/data/route53globalresolver/2022-09-27/service-2.json.gz b/py311/lib/python3.11/site-packages/botocore/data/route53globalresolver/2022-09-27/service-2.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..b9962b2666c2c6bc4ceec7eb478a2e337743ed92 --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/route53globalresolver/2022-09-27/service-2.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8916249816b8e04e2915bc23692eacf46850e300239852c7bbec6cba837183df +size 16643 diff --git a/py311/lib/python3.11/site-packages/botocore/data/s3outposts/2017-07-25/endpoint-rule-set-1.json.gz b/py311/lib/python3.11/site-packages/botocore/data/s3outposts/2017-07-25/endpoint-rule-set-1.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..e3db81ae7e92678bbe1b16c98107a6fa08c81d64 --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/s3outposts/2017-07-25/endpoint-rule-set-1.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5d91ff52ad557b46892e8cac62d8467c3584c2bb99aca6ca0895f06a530dd254 +size 1152 diff --git a/py311/lib/python3.11/site-packages/botocore/data/s3outposts/2017-07-25/service-2.json.gz b/py311/lib/python3.11/site-packages/botocore/data/s3outposts/2017-07-25/service-2.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..a6dda07768d09ce3ffc3ad2621c2df108ed216e4 --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/s3outposts/2017-07-25/service-2.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f9038b2ccf2fb9878db8b612ecc3da7d151e484455ff2870a7330bcdb759825e +size 3475 diff --git a/py311/lib/python3.11/site-packages/botocore/data/sagemaker-a2i-runtime/2019-11-07/endpoint-rule-set-1.json.gz b/py311/lib/python3.11/site-packages/botocore/data/sagemaker-a2i-runtime/2019-11-07/endpoint-rule-set-1.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..891823dc84a5e8ec9dd047a7db1d045499086278 --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/sagemaker-a2i-runtime/2019-11-07/endpoint-rule-set-1.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7c1782b8198fc577eefa60d4e37dbba7e93f66f300f251b8aa24eadafe3fe6af +size 1159 diff --git a/py311/lib/python3.11/site-packages/botocore/data/sagemaker-a2i-runtime/2019-11-07/service-2.json.gz b/py311/lib/python3.11/site-packages/botocore/data/sagemaker-a2i-runtime/2019-11-07/service-2.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..ec787cd442e132dba2ecf2a687e439028ff00e7c --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/sagemaker-a2i-runtime/2019-11-07/service-2.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c9e7897b2c30db95a5716ddfd17f4ecc3458822ff5212f07253f6e12c0c9cd1d +size 3798 diff --git a/py311/lib/python3.11/site-packages/botocore/data/secretsmanager/2017-10-17/endpoint-rule-set-1.json.gz b/py311/lib/python3.11/site-packages/botocore/data/secretsmanager/2017-10-17/endpoint-rule-set-1.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..331ca3c7527d4025fbd3444c132c5a0764a22d50 --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/secretsmanager/2017-10-17/endpoint-rule-set-1.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ed4dab0db10d38346c0b6ef9af1b3599786483d0c9f43fbd9035089c44178a8f +size 1351 diff --git a/py311/lib/python3.11/site-packages/botocore/data/secretsmanager/2017-10-17/service-2.json.gz b/py311/lib/python3.11/site-packages/botocore/data/secretsmanager/2017-10-17/service-2.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..f8c57089eb7f51eccbf9e0f60d84c277c15142b3 --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/secretsmanager/2017-10-17/service-2.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c205776d999a3f5a1b103aa35bf5c4fcafebba21164a95725af5e613ccbef386 +size 22383 diff --git a/py311/lib/python3.11/site-packages/botocore/data/security-ir/2018-05-10/endpoint-rule-set-1.json.gz b/py311/lib/python3.11/site-packages/botocore/data/security-ir/2018-05-10/endpoint-rule-set-1.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..edc4045b499dbc844e8c111b63746a95b345b23c --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/security-ir/2018-05-10/endpoint-rule-set-1.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9a8e6c322d05212b5cb1367c2d5b6adfcd5b947e348ab0b327e59be4eb6f57a8 +size 835 diff --git a/py311/lib/python3.11/site-packages/botocore/data/security-ir/2018-05-10/service-2.json.gz b/py311/lib/python3.11/site-packages/botocore/data/security-ir/2018-05-10/service-2.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..930814f197632903bc592d826fcd83837dc4e5d9 --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/security-ir/2018-05-10/service-2.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:77f405fd32c3769fe237e66e512d7d9b95fb6ee25546de43c0368c2b83d0c9dd +size 12359 diff --git a/py311/lib/python3.11/site-packages/botocore/data/snowball/2016-06-30/endpoint-rule-set-1.json.gz b/py311/lib/python3.11/site-packages/botocore/data/snowball/2016-06-30/endpoint-rule-set-1.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..94b718c6efb97fa9a8056e337355d73b1a4c0ea9 --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/snowball/2016-06-30/endpoint-rule-set-1.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:37a775a4c74c4d63d4d52f46170494f67e723a48a103caef98dc756d7d1fd0f4 +size 1150 diff --git a/py311/lib/python3.11/site-packages/botocore/data/snowball/2016-06-30/service-2.json.gz b/py311/lib/python3.11/site-packages/botocore/data/snowball/2016-06-30/service-2.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..a83699aeafa9604ab5e0b3a8690ea1c811735d97 --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/snowball/2016-06-30/service-2.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0953cbaa079134d1e2aac00a70971976d6059aa991d5940227c836eac20efd71 +size 17015 diff --git a/py311/lib/python3.11/site-packages/botocore/data/ssm-sap/2018-05-10/endpoint-rule-set-1.json.gz b/py311/lib/python3.11/site-packages/botocore/data/ssm-sap/2018-05-10/endpoint-rule-set-1.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..e69d5976ad3900bc13feaad49ad358344f22fc34 --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/ssm-sap/2018-05-10/endpoint-rule-set-1.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ec229b33e773acbac6fa6c9e6035e130a18109df67d063df56ed38acd60e9c31 +size 1298 diff --git a/py311/lib/python3.11/site-packages/botocore/data/ssm-sap/2018-05-10/service-2.json.gz b/py311/lib/python3.11/site-packages/botocore/data/ssm-sap/2018-05-10/service-2.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..d86d00ef2465252bf88b2c8793b132ef8b23ddf6 --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/ssm-sap/2018-05-10/service-2.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:121dd99efd028367a0268dd9b8e9522b99fca65b4a43b8f7f9cbe601e77b1246 +size 9460 diff --git a/py311/lib/python3.11/site-packages/botocore/data/sso-oidc/2019-06-10/endpoint-rule-set-1.json.gz b/py311/lib/python3.11/site-packages/botocore/data/sso-oidc/2019-06-10/endpoint-rule-set-1.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..9ce83b72e38ec06ee347795d622333a353090270 --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/sso-oidc/2019-06-10/endpoint-rule-set-1.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e900c9658c76f74029259c42ff7f9b8bd0bb291aeee0b0046a4fced13f4c2163 +size 1231 diff --git a/py311/lib/python3.11/site-packages/botocore/data/synthetics/2017-10-11/endpoint-rule-set-1.json.gz b/py311/lib/python3.11/site-packages/botocore/data/synthetics/2017-10-11/endpoint-rule-set-1.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..deabde72029399c10e1b71b7b422426690cd72f7 --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/synthetics/2017-10-11/endpoint-rule-set-1.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:01873edcc65f78ce029efaad767a7c93dc98570e37c73a3bd315734f0b41f57d +size 1150 diff --git a/py311/lib/python3.11/site-packages/botocore/data/synthetics/2017-10-11/service-2.json.gz b/py311/lib/python3.11/site-packages/botocore/data/synthetics/2017-10-11/service-2.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..a073316ffee5692064e66650d9e10442f5ae187e --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/synthetics/2017-10-11/service-2.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:655f11f5444c6d4dd98c42a5c7d78cd8e484cfc5efd645db8c1c77606b875915 +size 18363 diff --git a/py311/lib/python3.11/site-packages/botocore/data/timestream-influxdb/2023-01-27/endpoint-rule-set-1.json.gz b/py311/lib/python3.11/site-packages/botocore/data/timestream-influxdb/2023-01-27/endpoint-rule-set-1.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..190c1ea66ca187a23d150e516cad3fe83c8eefe7 --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/timestream-influxdb/2023-01-27/endpoint-rule-set-1.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:715d56453b11a53941232400ce9375b1d7e9c285615b066613222be3168e2b36 +size 1307 diff --git a/py311/lib/python3.11/site-packages/botocore/data/timestream-influxdb/2023-01-27/service-2.json.gz b/py311/lib/python3.11/site-packages/botocore/data/timestream-influxdb/2023-01-27/service-2.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..84fa856c034740a5b764d52c14598ead00604388 --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/timestream-influxdb/2023-01-27/service-2.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ad44900acb8ce263b5f0997bdae114055fdadf357bb59a22f87ec21c087faa18 +size 14484 diff --git a/py311/lib/python3.11/site-packages/botocore/data/verifiedpermissions/2021-12-01/endpoint-rule-set-1.json.gz b/py311/lib/python3.11/site-packages/botocore/data/verifiedpermissions/2021-12-01/endpoint-rule-set-1.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..01aa98038adf4d49c42be944fb0f40ba48c09040 --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/verifiedpermissions/2021-12-01/endpoint-rule-set-1.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:29015ad784932d6f4fc71fa5aca8f60975046e4830c0d6cd8a88becfafade3f4 +size 1306 diff --git a/py311/lib/python3.11/site-packages/botocore/data/verifiedpermissions/2021-12-01/service-2.json.gz b/py311/lib/python3.11/site-packages/botocore/data/verifiedpermissions/2021-12-01/service-2.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..04017b9089abd2cf1a93de7062ce8eca471b1e8b --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/verifiedpermissions/2021-12-01/service-2.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0a914e26326a5e8aa4e359448348ccd61db4c474093e2e66e8ee5db275067654 +size 25931 diff --git a/py311/lib/python3.11/site-packages/botocore/data/voice-id/2021-09-27/endpoint-rule-set-1.json.gz b/py311/lib/python3.11/site-packages/botocore/data/voice-id/2021-09-27/endpoint-rule-set-1.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..7a2c84b01cc2f85a1f330490fd6970b45086a8c9 --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/voice-id/2021-09-27/endpoint-rule-set-1.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0965316063cd57bf9d24ced4868e632f2875ad306878cafaa37fd12843ee6337 +size 1148 diff --git a/py311/lib/python3.11/site-packages/botocore/data/voice-id/2021-09-27/service-2.json.gz b/py311/lib/python3.11/site-packages/botocore/data/voice-id/2021-09-27/service-2.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..950d6610294f4be493d573b38e737b0c27949666 --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/voice-id/2021-09-27/service-2.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c88886983d8d0e676e6ce7530988a7c95eeffb44013cfbb7af780674b99c20c1 +size 11875 diff --git a/py311/lib/python3.11/site-packages/botocore/data/wellarchitected/2020-03-31/endpoint-rule-set-1.json.gz b/py311/lib/python3.11/site-packages/botocore/data/wellarchitected/2020-03-31/endpoint-rule-set-1.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..c39debd571be533b250254a15d0fb0c27ad8aa38 --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/wellarchitected/2020-03-31/endpoint-rule-set-1.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b13fb2b9dfc0ccabe1508cdcb52e43ae53ec8ca8cdd0f0ee7e5d07ec877d3560 +size 1154 diff --git a/py311/lib/python3.11/site-packages/botocore/data/wellarchitected/2020-03-31/service-2.json.gz b/py311/lib/python3.11/site-packages/botocore/data/wellarchitected/2020-03-31/service-2.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..80855e93a0320f8eadcf69f0e7e308f5c866355d --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/wellarchitected/2020-03-31/service-2.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:95f5f2037125c6ff0492e6b3c11864fd124b9043adefa493d1ac4ab81f1c5dec +size 21152 diff --git a/py311/lib/python3.11/site-packages/botocore/data/workspaces-thin-client/2023-08-22/endpoint-rule-set-1.json.gz b/py311/lib/python3.11/site-packages/botocore/data/workspaces-thin-client/2023-08-22/endpoint-rule-set-1.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..860e896b74bd5f551f57a313ba760eddfb94e49a --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/workspaces-thin-client/2023-08-22/endpoint-rule-set-1.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d6317b18104cb4e8067dc6e7d6322c897a6d284a888e581d36233c8329d062b8 +size 1297 diff --git a/py311/lib/python3.11/site-packages/botocore/data/workspaces-thin-client/2023-08-22/service-2.json.gz b/py311/lib/python3.11/site-packages/botocore/data/workspaces-thin-client/2023-08-22/service-2.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..34ae598289613730180cd4e1221c203d95c6a8d6 --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/workspaces-thin-client/2023-08-22/service-2.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9aa04d2ff161a72ba82a56bd4eb354069d6511e17bfa156ddd7530fdaa00358f +size 6460 diff --git a/py311/lib/python3.11/site-packages/botocore/data/xray/2016-04-12/service-2.json.gz b/py311/lib/python3.11/site-packages/botocore/data/xray/2016-04-12/service-2.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..403516c74520ee25cead0a93f99dbf3544dc83b0 --- /dev/null +++ b/py311/lib/python3.11/site-packages/botocore/data/xray/2016-04-12/service-2.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:28f431876dfd30aa3b3883bfd6fc46ba2d805caab67e718fa59732ec1643253a +size 21603 diff --git a/py311/lib/python3.11/site-packages/charset_normalizer/md__mypyc.cpython-311-x86_64-linux-gnu.so b/py311/lib/python3.11/site-packages/charset_normalizer/md__mypyc.cpython-311-x86_64-linux-gnu.so new file mode 100644 index 0000000000000000000000000000000000000000..ecfab5ce373a18d176019ab4d2a4a22a6c4e4e45 --- /dev/null +++ b/py311/lib/python3.11/site-packages/charset_normalizer/md__mypyc.cpython-311-x86_64-linux-gnu.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f375fd1766b20cf4688e13cf8bb0e99b62d2d29950046e01d76475192ae2da5c +size 282232 diff --git a/py311/lib/python3.11/site-packages/contourpy/_contourpy.cpython-311-x86_64-linux-gnu.so b/py311/lib/python3.11/site-packages/contourpy/_contourpy.cpython-311-x86_64-linux-gnu.so new file mode 100644 index 0000000000000000000000000000000000000000..8ecd9e6e2cc24b71fcea2fdc3ccf5c404d7385cb --- /dev/null +++ b/py311/lib/python3.11/site-packages/contourpy/_contourpy.cpython-311-x86_64-linux-gnu.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e76d5ad8b8240a88afd1214e7b6484cfe3535b1f2c3747da6ccfd6ad04dad7fe +size 928744 diff --git a/py311/lib/python3.11/site-packages/distlib/t64-arm.exe b/py311/lib/python3.11/site-packages/distlib/t64-arm.exe new file mode 100644 index 0000000000000000000000000000000000000000..4c236ed0ab8253ceee9276ddc3cf5ed9a7ea6a4c --- /dev/null +++ b/py311/lib/python3.11/site-packages/distlib/t64-arm.exe @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ebc4c06b7d95e74e315419ee7e88e1d0f71e9e9477538c00a93a9ff8c66a6cfc +size 182784 diff --git a/py311/lib/python3.11/site-packages/distlib/t64.exe b/py311/lib/python3.11/site-packages/distlib/t64.exe new file mode 100644 index 0000000000000000000000000000000000000000..3fd3d2df6541617229f21bdbb49fbbfc880b7f50 --- /dev/null +++ b/py311/lib/python3.11/site-packages/distlib/t64.exe @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:81a618f21cb87db9076134e70388b6e9cb7c2106739011b6a51772d22cae06b7 +size 108032 diff --git a/py311/lib/python3.11/site-packages/distlib/w64-arm.exe b/py311/lib/python3.11/site-packages/distlib/w64-arm.exe new file mode 100644 index 0000000000000000000000000000000000000000..bc02472528a32bd70ebfa2b0eca26e34a83fa264 --- /dev/null +++ b/py311/lib/python3.11/site-packages/distlib/w64-arm.exe @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c5dc9884a8f458371550e09bd396e5418bf375820a31b9899f6499bf391c7b2e +size 168448 diff --git a/py311/lib/python3.11/site-packages/distlib/w64.exe b/py311/lib/python3.11/site-packages/distlib/w64.exe new file mode 100644 index 0000000000000000000000000000000000000000..daebd1c30353b2d9b016069da83ad5b4c8ee86bd --- /dev/null +++ b/py311/lib/python3.11/site-packages/distlib/w64.exe @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7a319ffaba23a017d7b1e18ba726ba6c54c53d6446db55f92af53c279894f8ad +size 101888 diff --git a/py311/lib/python3.11/site-packages/flake8/api/__init__.py b/py311/lib/python3.11/site-packages/flake8/api/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..c5f9711a237a24b3b4f126d1330148a1308e8f4f --- /dev/null +++ b/py311/lib/python3.11/site-packages/flake8/api/__init__.py @@ -0,0 +1,6 @@ +"""Module containing all public entry-points for Flake8. + +This is the only submodule in Flake8 with a guaranteed stable API. All other +submodules are considered internal only and are subject to change. +""" +from __future__ import annotations diff --git a/py311/lib/python3.11/site-packages/flake8/api/legacy.py b/py311/lib/python3.11/site-packages/flake8/api/legacy.py new file mode 100644 index 0000000000000000000000000000000000000000..446df293d8f15f899315295bf234c26f9a9b6ae1 --- /dev/null +++ b/py311/lib/python3.11/site-packages/flake8/api/legacy.py @@ -0,0 +1,216 @@ +"""Module containing shims around Flake8 2.x behaviour. + +Previously, users would import :func:`get_style_guide` from ``flake8.engine``. +In 3.0 we no longer have an "engine" module but we maintain the API from it. +""" +from __future__ import annotations + +import argparse +import logging +import os.path +from typing import Any + +from flake8.discover_files import expand_paths +from flake8.formatting import base as formatter +from flake8.main import application as app +from flake8.options.parse_args import parse_args + +LOG = logging.getLogger(__name__) + + +__all__ = ("get_style_guide",) + + +class Report: + """Public facing object that mimic's Flake8 2.0's API. + + .. note:: + + There are important changes in how this object behaves compared to + the object provided in Flake8 2.x. + + .. warning:: + + This should not be instantiated by users. + + .. versionchanged:: 3.0.0 + """ + + def __init__(self, application: app.Application) -> None: + """Initialize the Report for the user. + + .. warning:: This should not be instantiated by users. + """ + assert application.guide is not None + self._application = application + self._style_guide = application.guide + self._stats = self._style_guide.stats + + @property + def total_errors(self) -> int: + """Return the total number of errors.""" + return self._application.result_count + + def get_statistics(self, violation: str) -> list[str]: + """Get the list of occurrences of a violation. + + :returns: + List of occurrences of a violation formatted as: + {Count} {Error Code} {Message}, e.g., + ``8 E531 Some error message about the error`` + """ + return [ + f"{s.count} {s.error_code} {s.message}" + for s in self._stats.statistics_for(violation) + ] + + +class StyleGuide: + """Public facing object that mimic's Flake8 2.0's StyleGuide. + + .. note:: + + There are important changes in how this object behaves compared to + the StyleGuide object provided in Flake8 2.x. + + .. warning:: + + This object should not be instantiated directly by users. + + .. versionchanged:: 3.0.0 + """ + + def __init__(self, application: app.Application) -> None: + """Initialize our StyleGuide.""" + self._application = application + self._file_checker_manager = application.file_checker_manager + + @property + def options(self) -> argparse.Namespace: + """Return application's options. + + An instance of :class:`argparse.Namespace` containing parsed options. + """ + assert self._application.options is not None + return self._application.options + + @property + def paths(self) -> list[str]: + """Return the extra arguments passed as paths.""" + assert self._application.options is not None + return self._application.options.filenames + + def check_files(self, paths: list[str] | None = None) -> Report: + """Run collected checks on the files provided. + + This will check the files passed in and return a :class:`Report` + instance. + + :param paths: + List of filenames (or paths) to check. + :returns: + Object that mimic's Flake8 2.0's Reporter class. + """ + assert self._application.options is not None + self._application.options.filenames = paths + self._application.run_checks() + self._application.report_errors() + return Report(self._application) + + def excluded(self, filename: str, parent: str | None = None) -> bool: + """Determine if a file is excluded. + + :param filename: + Path to the file to check if it is excluded. + :param parent: + Name of the parent directory containing the file. + :returns: + True if the filename is excluded, False otherwise. + """ + + def excluded(path: str) -> bool: + paths = tuple( + expand_paths( + paths=[path], + stdin_display_name=self.options.stdin_display_name, + filename_patterns=self.options.filename, + exclude=self.options.exclude, + ) + ) + return not paths + + return excluded(filename) or ( + parent is not None and excluded(os.path.join(parent, filename)) + ) + + def init_report( + self, + reporter: type[formatter.BaseFormatter] | None = None, + ) -> None: + """Set up a formatter for this run of Flake8.""" + if reporter is None: + return + if not issubclass(reporter, formatter.BaseFormatter): + raise ValueError( + "Report should be subclass of " + "flake8.formatter.BaseFormatter." + ) + self._application.formatter = reporter(self.options) + self._application.guide = None + # NOTE(sigmavirus24): This isn't the intended use of + # Application#make_guide but it works pretty well. + # Stop cringing... I know it's gross. + self._application.make_guide() + self._application.file_checker_manager = None + self._application.make_file_checker_manager([]) + + def input_file( + self, + filename: str, + lines: Any | None = None, + expected: Any | None = None, + line_offset: Any | None = 0, + ) -> Report: + """Run collected checks on a single file. + + This will check the file passed in and return a :class:`Report` + instance. + + :param filename: + The path to the file to check. + :param lines: + Ignored since Flake8 3.0. + :param expected: + Ignored since Flake8 3.0. + :param line_offset: + Ignored since Flake8 3.0. + :returns: + Object that mimic's Flake8 2.0's Reporter class. + """ + return self.check_files([filename]) + + +def get_style_guide(**kwargs: Any) -> StyleGuide: + r"""Provision a StyleGuide for use. + + :param \*\*kwargs: + Keyword arguments that provide some options for the StyleGuide. + :returns: + An initialized StyleGuide + """ + application = app.Application() + application.plugins, application.options = parse_args([]) + # We basically want application.initialize to be called but with these + # options set instead before we make our formatter, notifier, internal + # style guide and file checker manager. + options = application.options + for key, value in kwargs.items(): + try: + getattr(options, key) + setattr(options, key, value) + except AttributeError: + LOG.error('Could not update option "%s"', key) + application.make_formatter() + application.make_guide() + application.make_file_checker_manager([]) + return StyleGuide(application) diff --git a/py311/lib/python3.11/site-packages/flake8/formatting/__init__.py b/py311/lib/python3.11/site-packages/flake8/formatting/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..732d0b61c7ec8a8296bef71c861828e203a6fee5 --- /dev/null +++ b/py311/lib/python3.11/site-packages/flake8/formatting/__init__.py @@ -0,0 +1,2 @@ +"""Submodule containing the default formatters for Flake8.""" +from __future__ import annotations diff --git a/py311/lib/python3.11/site-packages/flake8/formatting/_windows_color.py b/py311/lib/python3.11/site-packages/flake8/formatting/_windows_color.py new file mode 100644 index 0000000000000000000000000000000000000000..a06fdb9456ad2f658cd0c1ebd7f225dc7fbdf3dc --- /dev/null +++ b/py311/lib/python3.11/site-packages/flake8/formatting/_windows_color.py @@ -0,0 +1,61 @@ +"""ctypes hackery to enable color processing on windows. + +See: https://github.com/pre-commit/pre-commit/blob/cb40e96/pre_commit/color.py +""" +from __future__ import annotations + +import sys + +if sys.platform == "win32": # pragma: no cover (windows) + + def _enable() -> None: + from ctypes import POINTER + from ctypes import windll + from ctypes import WinError + from ctypes import WINFUNCTYPE + from ctypes.wintypes import BOOL + from ctypes.wintypes import DWORD + from ctypes.wintypes import HANDLE + + STD_ERROR_HANDLE = -12 + ENABLE_VIRTUAL_TERMINAL_PROCESSING = 4 + + def bool_errcheck(result, func, args): + if not result: + raise WinError() + return args + + GetStdHandle = WINFUNCTYPE(HANDLE, DWORD)( + ("GetStdHandle", windll.kernel32), + ((1, "nStdHandle"),), + ) + + GetConsoleMode = WINFUNCTYPE(BOOL, HANDLE, POINTER(DWORD))( + ("GetConsoleMode", windll.kernel32), + ((1, "hConsoleHandle"), (2, "lpMode")), + ) + GetConsoleMode.errcheck = bool_errcheck + + SetConsoleMode = WINFUNCTYPE(BOOL, HANDLE, DWORD)( + ("SetConsoleMode", windll.kernel32), + ((1, "hConsoleHandle"), (1, "dwMode")), + ) + SetConsoleMode.errcheck = bool_errcheck + + # As of Windows 10, the Windows console supports (some) ANSI escape + # sequences, but it needs to be enabled using `SetConsoleMode` first. + # + # More info on the escape sequences supported: + # https://msdn.microsoft.com/en-us/library/windows/desktop/mt638032(v=vs.85).aspx + stderr = GetStdHandle(STD_ERROR_HANDLE) + flags = GetConsoleMode(stderr) + SetConsoleMode(stderr, flags | ENABLE_VIRTUAL_TERMINAL_PROCESSING) + + try: + _enable() + except OSError: + terminal_supports_color = False + else: + terminal_supports_color = True +else: # pragma: win32 no cover + terminal_supports_color = True diff --git a/py311/lib/python3.11/site-packages/flake8/formatting/base.py b/py311/lib/python3.11/site-packages/flake8/formatting/base.py new file mode 100644 index 0000000000000000000000000000000000000000..d986d651cc450751f40467ed9b288c76c695eeab --- /dev/null +++ b/py311/lib/python3.11/site-packages/flake8/formatting/base.py @@ -0,0 +1,202 @@ +"""The base class and interface for all formatting plugins.""" +from __future__ import annotations + +import argparse +import os +import sys +from typing import IO + +from flake8.formatting import _windows_color +from flake8.statistics import Statistics +from flake8.violation import Violation + + +class BaseFormatter: + """Class defining the formatter interface. + + .. attribute:: options + + The options parsed from both configuration files and the command-line. + + .. attribute:: filename + + If specified by the user, the path to store the results of the run. + + .. attribute:: output_fd + + Initialized when the :meth:`start` is called. This will be a file + object opened for writing. + + .. attribute:: newline + + The string to add to the end of a line. This is only used when the + output filename has been specified. + """ + + def __init__(self, options: argparse.Namespace) -> None: + """Initialize with the options parsed from config and cli. + + This also calls a hook, :meth:`after_init`, so subclasses do not need + to call super to call this method. + + :param options: + User specified configuration parsed from both configuration files + and the command-line interface. + """ + self.options = options + self.filename = options.output_file + self.output_fd: IO[str] | None = None + self.newline = "\n" + self.color = options.color == "always" or ( + options.color == "auto" + and sys.stdout.isatty() + and _windows_color.terminal_supports_color + ) + self.after_init() + + def after_init(self) -> None: + """Initialize the formatter further.""" + + def beginning(self, filename: str) -> None: + """Notify the formatter that we're starting to process a file. + + :param filename: + The name of the file that Flake8 is beginning to report results + from. + """ + + def finished(self, filename: str) -> None: + """Notify the formatter that we've finished processing a file. + + :param filename: + The name of the file that Flake8 has finished reporting results + from. + """ + + def start(self) -> None: + """Prepare the formatter to receive input. + + This defaults to initializing :attr:`output_fd` if :attr:`filename` + """ + if self.filename: + dirname = os.path.dirname(os.path.abspath(self.filename)) + os.makedirs(dirname, exist_ok=True) + self.output_fd = open(self.filename, "a") + + def handle(self, error: Violation) -> None: + """Handle an error reported by Flake8. + + This defaults to calling :meth:`format`, :meth:`show_source`, and + then :meth:`write`. To extend how errors are handled, override this + method. + + :param error: + This will be an instance of + :class:`~flake8.violation.Violation`. + """ + line = self.format(error) + source = self.show_source(error) + self.write(line, source) + + def format(self, error: Violation) -> str | None: + """Format an error reported by Flake8. + + This method **must** be implemented by subclasses. + + :param error: + This will be an instance of + :class:`~flake8.violation.Violation`. + :returns: + The formatted error string. + """ + raise NotImplementedError( + "Subclass of BaseFormatter did not implement" " format." + ) + + def show_statistics(self, statistics: Statistics) -> None: + """Format and print the statistics.""" + for error_code in statistics.error_codes(): + stats_for_error_code = statistics.statistics_for(error_code) + statistic = next(stats_for_error_code) + count = statistic.count + count += sum(stat.count for stat in stats_for_error_code) + self._write(f"{count:<5} {error_code} {statistic.message}") + + def show_benchmarks(self, benchmarks: list[tuple[str, float]]) -> None: + """Format and print the benchmarks.""" + # NOTE(sigmavirus24): The format strings are a little confusing, even + # to me, so here's a quick explanation: + # We specify the named value first followed by a ':' to indicate we're + # formatting the value. + # Next we use '<' to indicate we want the value left aligned. + # Then '10' is the width of the area. + # For floats, finally, we only want only want at most 3 digits after + # the decimal point to be displayed. This is the precision and it + # can not be specified for integers which is why we need two separate + # format strings. + float_format = "{value:<10.3} {statistic}".format + int_format = "{value:<10} {statistic}".format + for statistic, value in benchmarks: + if isinstance(value, int): + benchmark = int_format(statistic=statistic, value=value) + else: + benchmark = float_format(statistic=statistic, value=value) + self._write(benchmark) + + def show_source(self, error: Violation) -> str | None: + """Show the physical line generating the error. + + This also adds an indicator for the particular part of the line that + is reported as generating the problem. + + :param error: + This will be an instance of + :class:`~flake8.violation.Violation`. + :returns: + The formatted error string if the user wants to show the source. + If the user does not want to show the source, this will return + ``None``. + """ + if not self.options.show_source or error.physical_line is None: + return "" + + # Because column numbers are 1-indexed, we need to remove one to get + # the proper number of space characters. + indent = "".join( + c if c.isspace() else " " + for c in error.physical_line[: error.column_number - 1] + ) + # Physical lines have a newline at the end, no need to add an extra + # one + return f"{error.physical_line}{indent}^" + + def _write(self, output: str) -> None: + """Handle logic of whether to use an output file or print().""" + if self.output_fd is not None: + self.output_fd.write(output + self.newline) + if self.output_fd is None or self.options.tee: + sys.stdout.buffer.write(output.encode() + self.newline.encode()) + + def write(self, line: str | None, source: str | None) -> None: + """Write the line either to the output file or stdout. + + This handles deciding whether to write to a file or print to standard + out for subclasses. Override this if you want behaviour that differs + from the default. + + :param line: + The formatted string to print or write. + :param source: + The source code that has been formatted and associated with the + line of output. + """ + if line: + self._write(line) + if source: + self._write(source) + + def stop(self) -> None: + """Clean up after reporting is finished.""" + if self.output_fd is not None: + self.output_fd.close() + self.output_fd = None diff --git a/py311/lib/python3.11/site-packages/flake8/formatting/default.py b/py311/lib/python3.11/site-packages/flake8/formatting/default.py new file mode 100644 index 0000000000000000000000000000000000000000..b5d08ff0066f238ecc50e8be0e4d9dcb8f4a7e2b --- /dev/null +++ b/py311/lib/python3.11/site-packages/flake8/formatting/default.py @@ -0,0 +1,109 @@ +"""Default formatting class for Flake8.""" +from __future__ import annotations + +from flake8.formatting import base +from flake8.violation import Violation + +COLORS = { + "bold": "\033[1m", + "black": "\033[30m", + "red": "\033[31m", + "green": "\033[32m", + "yellow": "\033[33m", + "blue": "\033[34m", + "magenta": "\033[35m", + "cyan": "\033[36m", + "white": "\033[37m", + "reset": "\033[m", +} +COLORS_OFF = {k: "" for k in COLORS} + + +class SimpleFormatter(base.BaseFormatter): + """Simple abstraction for Default and Pylint formatter commonality. + + Sub-classes of this need to define an ``error_format`` attribute in order + to succeed. The ``format`` method relies on that attribute and expects the + ``error_format`` string to use the old-style formatting strings with named + parameters: + + * code + * text + * path + * row + * col + + """ + + error_format: str + + def format(self, error: Violation) -> str | None: + """Format and write error out. + + If an output filename is specified, write formatted errors to that + file. Otherwise, print the formatted error to standard out. + """ + return self.error_format % { + "code": error.code, + "text": error.text, + "path": error.filename, + "row": error.line_number, + "col": error.column_number, + **(COLORS if self.color else COLORS_OFF), + } + + +class Default(SimpleFormatter): + """Default formatter for Flake8. + + This also handles backwards compatibility for people specifying a custom + format string. + """ + + error_format = ( + "%(bold)s%(path)s%(reset)s" + "%(cyan)s:%(reset)s%(row)d%(cyan)s:%(reset)s%(col)d%(cyan)s:%(reset)s " + "%(bold)s%(red)s%(code)s%(reset)s %(text)s" + ) + + def after_init(self) -> None: + """Check for a custom format string.""" + if self.options.format.lower() != "default": + self.error_format = self.options.format + + +class Pylint(SimpleFormatter): + """Pylint formatter for Flake8.""" + + error_format = "%(path)s:%(row)d: [%(code)s] %(text)s" + + +class FilenameOnly(SimpleFormatter): + """Only print filenames, e.g., flake8 -q.""" + + error_format = "%(path)s" + + def after_init(self) -> None: + """Initialize our set of filenames.""" + self.filenames_already_printed: set[str] = set() + + def show_source(self, error: Violation) -> str | None: + """Do not include the source code.""" + + def format(self, error: Violation) -> str | None: + """Ensure we only print each error once.""" + if error.filename not in self.filenames_already_printed: + self.filenames_already_printed.add(error.filename) + return super().format(error) + else: + return None + + +class Nothing(base.BaseFormatter): + """Print absolutely nothing.""" + + def format(self, error: Violation) -> str | None: + """Do nothing.""" + + def show_source(self, error: Violation) -> str | None: + """Do not print the source.""" diff --git a/py311/lib/python3.11/site-packages/flake8/main/__init__.py b/py311/lib/python3.11/site-packages/flake8/main/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..85bcff426ad3c475adf6d7ecb2af102832d6d70a --- /dev/null +++ b/py311/lib/python3.11/site-packages/flake8/main/__init__.py @@ -0,0 +1,2 @@ +"""Module containing the logic for the Flake8 entry-points.""" +from __future__ import annotations diff --git a/py311/lib/python3.11/site-packages/flake8/main/application.py b/py311/lib/python3.11/site-packages/flake8/main/application.py new file mode 100644 index 0000000000000000000000000000000000000000..4704cbd5d210e5f30f2fe1ba11a4c9f20c054b62 --- /dev/null +++ b/py311/lib/python3.11/site-packages/flake8/main/application.py @@ -0,0 +1,215 @@ +"""Module containing the application logic for Flake8.""" +from __future__ import annotations + +import argparse +import json +import logging +import time +from collections.abc import Sequence + +import flake8 +from flake8 import checker +from flake8 import defaults +from flake8 import exceptions +from flake8 import style_guide +from flake8.formatting.base import BaseFormatter +from flake8.main import debug +from flake8.options.parse_args import parse_args +from flake8.plugins import finder +from flake8.plugins import reporter + + +LOG = logging.getLogger(__name__) + + +class Application: + """Abstract our application into a class.""" + + def __init__(self) -> None: + """Initialize our application.""" + #: The timestamp when the Application instance was instantiated. + self.start_time = time.time() + #: The timestamp when the Application finished reported errors. + self.end_time: float | None = None + + self.plugins: finder.Plugins | None = None + #: The user-selected formatter from :attr:`formatting_plugins` + self.formatter: BaseFormatter | None = None + #: The :class:`flake8.style_guide.StyleGuideManager` built from the + #: user's options + self.guide: style_guide.StyleGuideManager | None = None + #: The :class:`flake8.checker.Manager` that will handle running all of + #: the checks selected by the user. + self.file_checker_manager: checker.Manager | None = None + + #: The user-supplied options parsed into an instance of + #: :class:`argparse.Namespace` + self.options: argparse.Namespace | None = None + #: The number of errors, warnings, and other messages after running + #: flake8 and taking into account ignored errors and lines. + self.result_count = 0 + #: The total number of errors before accounting for ignored errors and + #: lines. + self.total_result_count = 0 + #: Whether or not something catastrophic happened and we should exit + #: with a non-zero status code + self.catastrophic_failure = False + + def exit_code(self) -> int: + """Return the program exit code.""" + if self.catastrophic_failure: + return 1 + assert self.options is not None + if self.options.exit_zero: + return 0 + else: + return int(self.result_count > 0) + + def make_formatter(self) -> None: + """Initialize a formatter based on the parsed options.""" + assert self.plugins is not None + assert self.options is not None + self.formatter = reporter.make(self.plugins.reporters, self.options) + + def make_guide(self) -> None: + """Initialize our StyleGuide.""" + assert self.formatter is not None + assert self.options is not None + self.guide = style_guide.StyleGuideManager( + self.options, self.formatter + ) + + def make_file_checker_manager(self, argv: Sequence[str]) -> None: + """Initialize our FileChecker Manager.""" + assert self.guide is not None + assert self.plugins is not None + self.file_checker_manager = checker.Manager( + style_guide=self.guide, + plugins=self.plugins.checkers, + argv=argv, + ) + + def run_checks(self) -> None: + """Run the actual checks with the FileChecker Manager. + + This method encapsulates the logic to make a + :class:`~flake8.checker.Manger` instance run the checks it is + managing. + """ + assert self.file_checker_manager is not None + + self.file_checker_manager.start() + try: + self.file_checker_manager.run() + except exceptions.PluginExecutionFailed as plugin_failed: + print(str(plugin_failed)) + print("Run flake8 with greater verbosity to see more details") + self.catastrophic_failure = True + LOG.info("Finished running") + self.file_checker_manager.stop() + self.end_time = time.time() + + def report_benchmarks(self) -> None: + """Aggregate, calculate, and report benchmarks for this run.""" + assert self.options is not None + if not self.options.benchmark: + return + + assert self.file_checker_manager is not None + assert self.end_time is not None + time_elapsed = self.end_time - self.start_time + statistics = [("seconds elapsed", time_elapsed)] + add_statistic = statistics.append + for statistic in defaults.STATISTIC_NAMES + ("files",): + value = self.file_checker_manager.statistics[statistic] + total_description = f"total {statistic} processed" + add_statistic((total_description, value)) + per_second_description = f"{statistic} processed per second" + add_statistic((per_second_description, int(value / time_elapsed))) + + assert self.formatter is not None + self.formatter.show_benchmarks(statistics) + + def report_errors(self) -> None: + """Report all the errors found by flake8 3.0. + + This also updates the :attr:`result_count` attribute with the total + number of errors, warnings, and other messages found. + """ + LOG.info("Reporting errors") + assert self.file_checker_manager is not None + results = self.file_checker_manager.report() + self.total_result_count, self.result_count = results + LOG.info( + "Found a total of %d violations and reported %d", + self.total_result_count, + self.result_count, + ) + + def report_statistics(self) -> None: + """Aggregate and report statistics from this run.""" + assert self.options is not None + if not self.options.statistics: + return + + assert self.formatter is not None + assert self.guide is not None + self.formatter.show_statistics(self.guide.stats) + + def initialize(self, argv: Sequence[str]) -> None: + """Initialize the application to be run. + + This finds the plugins, registers their options, and parses the + command-line arguments. + """ + self.plugins, self.options = parse_args(argv) + + if self.options.bug_report: + info = debug.information(flake8.__version__, self.plugins) + print(json.dumps(info, indent=2, sort_keys=True)) + raise SystemExit(0) + + self.make_formatter() + self.make_guide() + self.make_file_checker_manager(argv) + + def report(self) -> None: + """Report errors, statistics, and benchmarks.""" + assert self.formatter is not None + self.formatter.start() + self.report_errors() + self.report_statistics() + self.report_benchmarks() + self.formatter.stop() + + def _run(self, argv: Sequence[str]) -> None: + self.initialize(argv) + self.run_checks() + self.report() + + def run(self, argv: Sequence[str]) -> None: + """Run our application. + + This method will also handle KeyboardInterrupt exceptions for the + entirety of the flake8 application. If it sees a KeyboardInterrupt it + will forcibly clean up the :class:`~flake8.checker.Manager`. + """ + try: + self._run(argv) + except KeyboardInterrupt as exc: + print("... stopped") + LOG.critical("Caught keyboard interrupt from user") + LOG.exception(exc) + self.catastrophic_failure = True + except exceptions.ExecutionError as exc: + print("There was a critical error during execution of Flake8:") + print(exc) + LOG.exception(exc) + self.catastrophic_failure = True + except exceptions.EarlyQuit: + self.catastrophic_failure = True + print("... stopped while processing files") + else: + assert self.options is not None + if self.options.count: + print(self.result_count) diff --git a/py311/lib/python3.11/site-packages/flake8/main/cli.py b/py311/lib/python3.11/site-packages/flake8/main/cli.py new file mode 100644 index 0000000000000000000000000000000000000000..1a52f36daf67b19e362082867a2900361a37141c --- /dev/null +++ b/py311/lib/python3.11/site-packages/flake8/main/cli.py @@ -0,0 +1,24 @@ +"""Command-line implementation of flake8.""" +from __future__ import annotations + +import sys +from collections.abc import Sequence + +from flake8.main import application + + +def main(argv: Sequence[str] | None = None) -> int: + """Execute the main bit of the application. + + This handles the creation of an instance of :class:`Application`, runs it, + and then exits the application. + + :param argv: + The arguments to be passed to the application for parsing. + """ + if argv is None: + argv = sys.argv[1:] + + app = application.Application() + app.run(argv) + return app.exit_code() diff --git a/py311/lib/python3.11/site-packages/flake8/main/debug.py b/py311/lib/python3.11/site-packages/flake8/main/debug.py new file mode 100644 index 0000000000000000000000000000000000000000..c3a8b0b79aee91810f2c82601d0fecdeecafa135 --- /dev/null +++ b/py311/lib/python3.11/site-packages/flake8/main/debug.py @@ -0,0 +1,30 @@ +"""Module containing the logic for our debugging logic.""" +from __future__ import annotations + +import platform +from typing import Any + +from flake8.plugins.finder import Plugins + + +def information(version: str, plugins: Plugins) -> dict[str, Any]: + """Generate the information to be printed for the bug report.""" + versions = sorted( + { + (loaded.plugin.package, loaded.plugin.version) + for loaded in plugins.all_plugins() + if loaded.plugin.package not in {"flake8", "local"} + } + ) + return { + "version": version, + "plugins": [ + {"plugin": plugin, "version": version} + for plugin, version in versions + ], + "platform": { + "python_implementation": platform.python_implementation(), + "python_version": platform.python_version(), + "system": platform.system(), + }, + } diff --git a/py311/lib/python3.11/site-packages/flake8/main/options.py b/py311/lib/python3.11/site-packages/flake8/main/options.py new file mode 100644 index 0000000000000000000000000000000000000000..9d57321b852b87d70b44dc85a6338253e727eabe --- /dev/null +++ b/py311/lib/python3.11/site-packages/flake8/main/options.py @@ -0,0 +1,396 @@ +"""Contains the logic for all of the default options for Flake8.""" +from __future__ import annotations + +import argparse + +from flake8 import defaults +from flake8.options.manager import OptionManager + + +def stage1_arg_parser() -> argparse.ArgumentParser: + """Register the preliminary options on our OptionManager. + + The preliminary options include: + + - ``-v``/``--verbose`` + - ``--output-file`` + - ``--append-config`` + - ``--config`` + - ``--isolated`` + - ``--enable-extensions`` + """ + parser = argparse.ArgumentParser(add_help=False) + + parser.add_argument( + "-v", + "--verbose", + default=0, + action="count", + help="Print more information about what is happening in flake8. " + "This option is repeatable and will increase verbosity each " + "time it is repeated.", + ) + + parser.add_argument( + "--output-file", default=None, help="Redirect report to a file." + ) + + # Config file options + + parser.add_argument( + "--append-config", + action="append", + default=[], + help="Provide extra config files to parse in addition to the files " + "found by Flake8 by default. These files are the last ones read " + "and so they take the highest precedence when multiple files " + "provide the same option.", + ) + + parser.add_argument( + "--config", + default=None, + help="Path to the config file that will be the authoritative config " + "source. This will cause Flake8 to ignore all other " + "configuration files.", + ) + + parser.add_argument( + "--isolated", + default=False, + action="store_true", + help="Ignore all configuration files.", + ) + + # Plugin enablement options + + parser.add_argument( + "--enable-extensions", + help="Enable plugins and extensions that are otherwise disabled " + "by default", + ) + + parser.add_argument( + "--require-plugins", + help="Require specific plugins to be installed before running", + ) + + return parser + + +class JobsArgument: + """Type callback for the --jobs argument.""" + + def __init__(self, arg: str) -> None: + """Parse and validate the --jobs argument. + + :param arg: The argument passed by argparse for validation + """ + self.is_auto = False + self.n_jobs = -1 + if arg == "auto": + self.is_auto = True + elif arg.isdigit(): + self.n_jobs = int(arg) + else: + raise argparse.ArgumentTypeError( + f"{arg!r} must be 'auto' or an integer.", + ) + + def __repr__(self) -> str: + """Representation for debugging.""" + return f"{type(self).__name__}({str(self)!r})" + + def __str__(self) -> str: + """Format our JobsArgument class.""" + return "auto" if self.is_auto else str(self.n_jobs) + + +def register_default_options(option_manager: OptionManager) -> None: + """Register the default options on our OptionManager. + + The default options include: + + - ``-q``/``--quiet`` + - ``--color`` + - ``--count`` + - ``--exclude`` + - ``--extend-exclude`` + - ``--filename`` + - ``--format`` + - ``--hang-closing`` + - ``--ignore`` + - ``--extend-ignore`` + - ``--per-file-ignores`` + - ``--max-line-length`` + - ``--max-doc-length`` + - ``--indent-size`` + - ``--select`` + - ``--extend-select`` + - ``--disable-noqa`` + - ``--show-source`` + - ``--statistics`` + - ``--exit-zero`` + - ``-j``/``--jobs`` + - ``--tee`` + - ``--benchmark`` + - ``--bug-report`` + """ + add_option = option_manager.add_option + + add_option( + "-q", + "--quiet", + default=0, + action="count", + parse_from_config=True, + help="Report only file names, or nothing. This option is repeatable.", + ) + + add_option( + "--color", + choices=("auto", "always", "never"), + default="auto", + help="Whether to use color in output. Defaults to `%(default)s`.", + ) + + add_option( + "--count", + action="store_true", + parse_from_config=True, + help="Print total number of errors to standard output after " + "all other output.", + ) + + add_option( + "--exclude", + metavar="patterns", + default=",".join(defaults.EXCLUDE), + comma_separated_list=True, + parse_from_config=True, + normalize_paths=True, + help="Comma-separated list of files or directories to exclude. " + "(Default: %(default)s)", + ) + + add_option( + "--extend-exclude", + metavar="patterns", + default="", + parse_from_config=True, + comma_separated_list=True, + normalize_paths=True, + help="Comma-separated list of files or directories to add to the list " + "of excluded ones.", + ) + + add_option( + "--filename", + metavar="patterns", + default="*.py", + parse_from_config=True, + comma_separated_list=True, + help="Only check for filenames matching the patterns in this comma-" + "separated list. (Default: %(default)s)", + ) + + add_option( + "--stdin-display-name", + default="stdin", + help="The name used when reporting errors from code passed via stdin. " + "This is useful for editors piping the file contents to flake8. " + "(Default: %(default)s)", + ) + + # TODO(sigmavirus24): Figure out --first/--repeat + + # NOTE(sigmavirus24): We can't use choices for this option since users can + # freely provide a format string and that will break if we restrict their + # choices. + add_option( + "--format", + metavar="format", + default="default", + parse_from_config=True, + help=( + f"Format errors according to the chosen formatter " + f"({', '.join(sorted(option_manager.formatter_names))}) " + f"or a format string containing %%-style " + f"mapping keys (code, col, path, row, text). " + f"For example, " + f"``--format=pylint`` or ``--format='%%(path)s %%(code)s'``. " + f"(Default: %(default)s)" + ), + ) + + add_option( + "--hang-closing", + action="store_true", + parse_from_config=True, + help="Hang closing bracket instead of matching indentation of opening " + "bracket's line.", + ) + + add_option( + "--ignore", + metavar="errors", + parse_from_config=True, + comma_separated_list=True, + help=( + f"Comma-separated list of error codes to ignore (or skip). " + f"For example, ``--ignore=E4,E51,W234``. " + f"(Default: {','.join(defaults.IGNORE)})" + ), + ) + + add_option( + "--extend-ignore", + metavar="errors", + parse_from_config=True, + comma_separated_list=True, + help="Comma-separated list of error codes to add to the list of " + "ignored ones. For example, ``--extend-ignore=E4,E51,W234``.", + ) + + add_option( + "--per-file-ignores", + default="", + parse_from_config=True, + help="A pairing of filenames and violation codes that defines which " + "violations to ignore in a particular file. The filenames can be " + "specified in a manner similar to the ``--exclude`` option and the " + "violations work similarly to the ``--ignore`` and ``--select`` " + "options.", + ) + + add_option( + "--max-line-length", + type=int, + metavar="n", + default=defaults.MAX_LINE_LENGTH, + parse_from_config=True, + help="Maximum allowed line length for the entirety of this run. " + "(Default: %(default)s)", + ) + + add_option( + "--max-doc-length", + type=int, + metavar="n", + default=None, + parse_from_config=True, + help="Maximum allowed doc line length for the entirety of this run. " + "(Default: %(default)s)", + ) + add_option( + "--indent-size", + type=int, + metavar="n", + default=defaults.INDENT_SIZE, + parse_from_config=True, + help="Number of spaces used for indentation (Default: %(default)s)", + ) + + add_option( + "--select", + metavar="errors", + parse_from_config=True, + comma_separated_list=True, + help=( + "Limit the reported error codes to codes prefix-matched by this " + "list. " + "You usually do not need to specify this option as the default " + "includes all installed plugin codes. " + "For example, ``--select=E4,E51,W234``." + ), + ) + + add_option( + "--extend-select", + metavar="errors", + parse_from_config=True, + comma_separated_list=True, + help=( + "Add additional error codes to the default ``--select``. " + "You usually do not need to specify this option as the default " + "includes all installed plugin codes. " + "For example, ``--extend-select=E4,E51,W234``." + ), + ) + + add_option( + "--disable-noqa", + default=False, + parse_from_config=True, + action="store_true", + help='Disable the effect of "# noqa". This will report errors on ' + 'lines with "# noqa" at the end.', + ) + + # TODO(sigmavirus24): Decide what to do about --show-pep8 + + add_option( + "--show-source", + action="store_true", + parse_from_config=True, + help="Show the source generate each error or warning.", + ) + add_option( + "--no-show-source", + action="store_false", + dest="show_source", + parse_from_config=False, + help="Negate --show-source", + ) + + add_option( + "--statistics", + action="store_true", + parse_from_config=True, + help="Count errors.", + ) + + # Flake8 options + + add_option( + "--exit-zero", + action="store_true", + help='Exit with status code "0" even if there are errors.', + ) + + add_option( + "-j", + "--jobs", + default="auto", + parse_from_config=True, + type=JobsArgument, + help="Number of subprocesses to use to run checks in parallel. " + 'This is ignored on Windows. The default, "auto", will ' + "auto-detect the number of processors available to use. " + "(Default: %(default)s)", + ) + + add_option( + "--tee", + default=False, + parse_from_config=True, + action="store_true", + help="Write to stdout and output-file.", + ) + + # Benchmarking + + add_option( + "--benchmark", + default=False, + action="store_true", + help="Print benchmark information about this run of Flake8", + ) + + # Debugging + + add_option( + "--bug-report", + action="store_true", + help="Print information necessary when preparing a bug report", + ) diff --git a/py311/lib/python3.11/site-packages/flake8/options/__init__.py b/py311/lib/python3.11/site-packages/flake8/options/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..3578223bbc18c2052957b3365db97f25cb1f06d1 --- /dev/null +++ b/py311/lib/python3.11/site-packages/flake8/options/__init__.py @@ -0,0 +1,13 @@ +"""Package containing the option manager and config management logic. + +- :mod:`flake8.options.config` contains the logic for finding, parsing, and + merging configuration files. + +- :mod:`flake8.options.manager` contains the logic for managing customized + Flake8 command-line and configuration options. + +- :mod:`flake8.options.aggregator` uses objects from both of the above modules + to aggregate configuration into one object used by plugins and Flake8. + +""" +from __future__ import annotations diff --git a/py311/lib/python3.11/site-packages/flake8/options/aggregator.py b/py311/lib/python3.11/site-packages/flake8/options/aggregator.py new file mode 100644 index 0000000000000000000000000000000000000000..999161ab86be4a3e6d375cb4b8b571963e9dc804 --- /dev/null +++ b/py311/lib/python3.11/site-packages/flake8/options/aggregator.py @@ -0,0 +1,56 @@ +"""Aggregation function for CLI specified options and config file options. + +This holds the logic that uses the collected and merged config files and +applies the user-specified command-line configuration on top of it. +""" +from __future__ import annotations + +import argparse +import configparser +import logging +from collections.abc import Sequence + +from flake8.options import config +from flake8.options.manager import OptionManager + +LOG = logging.getLogger(__name__) + + +def aggregate_options( + manager: OptionManager, + cfg: configparser.RawConfigParser, + cfg_dir: str, + argv: Sequence[str] | None, +) -> argparse.Namespace: + """Aggregate and merge CLI and config file options.""" + # Get defaults from the option parser + default_values = manager.parse_args([]) + + # Get the parsed config + parsed_config = config.parse_config(manager, cfg, cfg_dir) + + # store the plugin-set extended default ignore / select + default_values.extended_default_ignore = manager.extended_default_ignore + default_values.extended_default_select = manager.extended_default_select + + # Merge values parsed from config onto the default values returned + for config_name, value in parsed_config.items(): + dest_name = config_name + # If the config name is somehow different from the destination name, + # fetch the destination name from our Option + if not hasattr(default_values, config_name): + dest_val = manager.config_options_dict[config_name].dest + assert isinstance(dest_val, str) + dest_name = dest_val + + LOG.debug( + 'Overriding default value of (%s) for "%s" with (%s)', + getattr(default_values, dest_name, None), + dest_name, + value, + ) + # Override the default values with the config values + setattr(default_values, dest_name, value) + + # Finally parse the command-line options + return manager.parse_args(argv, default_values) diff --git a/py311/lib/python3.11/site-packages/flake8/options/config.py b/py311/lib/python3.11/site-packages/flake8/options/config.py new file mode 100644 index 0000000000000000000000000000000000000000..b51949c2ec7641f546a2d4cf12beafe4efd1a3d2 --- /dev/null +++ b/py311/lib/python3.11/site-packages/flake8/options/config.py @@ -0,0 +1,140 @@ +"""Config handling logic for Flake8.""" +from __future__ import annotations + +import configparser +import logging +import os.path +from typing import Any + +from flake8 import exceptions +from flake8.defaults import VALID_CODE_PREFIX +from flake8.options.manager import OptionManager + +LOG = logging.getLogger(__name__) + + +def _stat_key(s: str) -> tuple[int, int]: + # same as what's used by samefile / samestat + st = os.stat(s) + return st.st_ino, st.st_dev + + +def _find_config_file(path: str) -> str | None: + # on windows if the homedir isn't detected this returns back `~` + home = os.path.expanduser("~") + try: + home_stat = _stat_key(home) if home != "~" else None + except OSError: # FileNotFoundError / PermissionError / etc. + home_stat = None + + dir_stat = _stat_key(path) + while True: + for candidate in ("setup.cfg", "tox.ini", ".flake8"): + cfg = configparser.RawConfigParser() + cfg_path = os.path.join(path, candidate) + try: + cfg.read(cfg_path, encoding="UTF-8") + except (UnicodeDecodeError, configparser.ParsingError) as e: + LOG.warning("ignoring unparseable config %s: %s", cfg_path, e) + else: + # only consider it a config if it contains flake8 sections + if "flake8" in cfg or "flake8:local-plugins" in cfg: + return cfg_path + + new_path = os.path.dirname(path) + new_dir_stat = _stat_key(new_path) + if new_dir_stat == dir_stat or new_dir_stat == home_stat: + break + else: + path = new_path + dir_stat = new_dir_stat + + # did not find any configuration file + return None + + +def load_config( + config: str | None, + extra: list[str], + *, + isolated: bool = False, +) -> tuple[configparser.RawConfigParser, str]: + """Load the configuration given the user options. + + - in ``isolated`` mode, return an empty configuration + - if a config file is given in ``config`` use that, otherwise attempt to + discover a configuration using ``tox.ini`` / ``setup.cfg`` / ``.flake8`` + - finally, load any ``extra`` configuration files + """ + pwd = os.path.abspath(".") + + if isolated: + return configparser.RawConfigParser(), pwd + + if config is None: + config = _find_config_file(pwd) + + cfg = configparser.RawConfigParser() + if config is not None: + if not cfg.read(config, encoding="UTF-8"): + raise exceptions.ExecutionError( + f"The specified config file does not exist: {config}" + ) + cfg_dir = os.path.dirname(config) + else: + cfg_dir = pwd + + # TODO: remove this and replace it with configuration modifying plugins + # read the additional configs afterwards + for filename in extra: + if not cfg.read(filename, encoding="UTF-8"): + raise exceptions.ExecutionError( + f"The specified config file does not exist: {filename}" + ) + + return cfg, cfg_dir + + +def parse_config( + option_manager: OptionManager, + cfg: configparser.RawConfigParser, + cfg_dir: str, +) -> dict[str, Any]: + """Parse and normalize the typed configuration options.""" + if "flake8" not in cfg: + return {} + + config_dict = {} + + for option_name in cfg["flake8"]: + option = option_manager.config_options_dict.get(option_name) + if option is None: + LOG.debug('Option "%s" is not registered. Ignoring.', option_name) + continue + + # Use the appropriate method to parse the config value + value: Any + if option.type is int or option.action == "count": + value = cfg.getint("flake8", option_name) + elif option.action in {"store_true", "store_false"}: + value = cfg.getboolean("flake8", option_name) + else: + value = cfg.get("flake8", option_name) + + LOG.debug('Option "%s" returned value: %r', option_name, value) + + final_value = option.normalize(value, cfg_dir) + + if option_name in {"ignore", "extend-ignore"}: + for error_code in final_value: + if not VALID_CODE_PREFIX.match(error_code): + raise ValueError( + f"Error code {error_code!r} " + f"supplied to {option_name!r} option " + f"does not match {VALID_CODE_PREFIX.pattern!r}" + ) + + assert option.config_name is not None + config_dict[option.config_name] = final_value + + return config_dict diff --git a/py311/lib/python3.11/site-packages/flake8/options/manager.py b/py311/lib/python3.11/site-packages/flake8/options/manager.py new file mode 100644 index 0000000000000000000000000000000000000000..cb195fe281eaa229461203d41b64a7075c4c68e0 --- /dev/null +++ b/py311/lib/python3.11/site-packages/flake8/options/manager.py @@ -0,0 +1,320 @@ +"""Option handling and Option management logic.""" +from __future__ import annotations + +import argparse +import enum +import functools +import logging +from collections.abc import Sequence +from typing import Any +from typing import Callable + +from flake8 import utils +from flake8.plugins.finder import Plugins + +LOG = logging.getLogger(__name__) + +# represent a singleton of "not passed arguments". +# an enum is chosen to trick mypy +_ARG = enum.Enum("_ARG", "NO") + + +def _flake8_normalize( + value: str, + *args: str, + comma_separated_list: bool = False, + normalize_paths: bool = False, +) -> str | list[str]: + ret: str | list[str] = value + if comma_separated_list and isinstance(ret, str): + ret = utils.parse_comma_separated_list(value) + + if normalize_paths: + if isinstance(ret, str): + ret = utils.normalize_path(ret, *args) + else: + ret = utils.normalize_paths(ret, *args) + + return ret + + +class Option: + """Our wrapper around an argparse argument parsers to add features.""" + + def __init__( + self, + short_option_name: str | _ARG = _ARG.NO, + long_option_name: str | _ARG = _ARG.NO, + # Options below are taken from argparse.ArgumentParser.add_argument + action: str | type[argparse.Action] | _ARG = _ARG.NO, + default: Any | _ARG = _ARG.NO, + type: Callable[..., Any] | _ARG = _ARG.NO, + dest: str | _ARG = _ARG.NO, + nargs: int | str | _ARG = _ARG.NO, + const: Any | _ARG = _ARG.NO, + choices: Sequence[Any] | _ARG = _ARG.NO, + help: str | _ARG = _ARG.NO, + metavar: str | _ARG = _ARG.NO, + required: bool | _ARG = _ARG.NO, + # Options below here are specific to Flake8 + parse_from_config: bool = False, + comma_separated_list: bool = False, + normalize_paths: bool = False, + ) -> None: + """Initialize an Option instance. + + The following are all passed directly through to argparse. + + :param short_option_name: + The short name of the option (e.g., ``-x``). This will be the + first argument passed to ``ArgumentParser.add_argument`` + :param long_option_name: + The long name of the option (e.g., ``--xtra-long-option``). This + will be the second argument passed to + ``ArgumentParser.add_argument`` + :param default: + Default value of the option. + :param dest: + Attribute name to store parsed option value as. + :param nargs: + Number of arguments to parse for this option. + :param const: + Constant value to store on a common destination. Usually used in + conjunction with ``action="store_const"``. + :param choices: + Possible values for the option. + :param help: + Help text displayed in the usage information. + :param metavar: + Name to use instead of the long option name for help text. + :param required: + Whether this option is required or not. + + The following options may be passed directly through to :mod:`argparse` + but may need some massaging. + + :param type: + A callable to normalize the type (as is the case in + :mod:`argparse`). + :param action: + Any action allowed by :mod:`argparse`. + + The following parameters are for Flake8's option handling alone. + + :param parse_from_config: + Whether or not this option should be parsed out of config files. + :param comma_separated_list: + Whether the option is a comma separated list when parsing from a + config file. + :param normalize_paths: + Whether the option is expecting a path or list of paths and should + attempt to normalize the paths to absolute paths. + """ + if ( + long_option_name is _ARG.NO + and short_option_name is not _ARG.NO + and short_option_name.startswith("--") + ): + short_option_name, long_option_name = _ARG.NO, short_option_name + + # flake8 special type normalization + if comma_separated_list or normalize_paths: + type = functools.partial( + _flake8_normalize, + comma_separated_list=comma_separated_list, + normalize_paths=normalize_paths, + ) + + self.short_option_name = short_option_name + self.long_option_name = long_option_name + self.option_args = [ + x + for x in (short_option_name, long_option_name) + if x is not _ARG.NO + ] + self.action = action + self.default = default + self.type = type + self.dest = dest + self.nargs = nargs + self.const = const + self.choices = choices + self.help = help + self.metavar = metavar + self.required = required + self.option_kwargs: dict[str, Any | _ARG] = { + "action": self.action, + "default": self.default, + "type": self.type, + "dest": self.dest, + "nargs": self.nargs, + "const": self.const, + "choices": self.choices, + "help": self.help, + "metavar": self.metavar, + "required": self.required, + } + + # Set our custom attributes + self.parse_from_config = parse_from_config + self.comma_separated_list = comma_separated_list + self.normalize_paths = normalize_paths + + self.config_name: str | None = None + if parse_from_config: + if long_option_name is _ARG.NO: + raise ValueError( + "When specifying parse_from_config=True, " + "a long_option_name must also be specified." + ) + self.config_name = long_option_name[2:].replace("-", "_") + + self._opt = None + + @property + def filtered_option_kwargs(self) -> dict[str, Any]: + """Return any actually-specified arguments.""" + return { + k: v for k, v in self.option_kwargs.items() if v is not _ARG.NO + } + + def __repr__(self) -> str: # noqa: D105 + parts = [] + for arg in self.option_args: + parts.append(arg) + for k, v in self.filtered_option_kwargs.items(): + parts.append(f"{k}={v!r}") + return f"Option({', '.join(parts)})" + + def normalize(self, value: Any, *normalize_args: str) -> Any: + """Normalize the value based on the option configuration.""" + if self.comma_separated_list and isinstance(value, str): + value = utils.parse_comma_separated_list(value) + + if self.normalize_paths: + if isinstance(value, list): + value = utils.normalize_paths(value, *normalize_args) + else: + value = utils.normalize_path(value, *normalize_args) + + return value + + def to_argparse(self) -> tuple[list[str], dict[str, Any]]: + """Convert a Flake8 Option to argparse ``add_argument`` arguments.""" + return self.option_args, self.filtered_option_kwargs + + +class OptionManager: + """Manage Options and OptionParser while adding post-processing.""" + + def __init__( + self, + *, + version: str, + plugin_versions: str, + parents: list[argparse.ArgumentParser], + formatter_names: list[str], + ) -> None: + """Initialize an instance of an OptionManager.""" + self.formatter_names = formatter_names + self.parser = argparse.ArgumentParser( + prog="flake8", + usage="%(prog)s [options] file file ...", + parents=parents, + epilog=f"Installed plugins: {plugin_versions}", + ) + self.parser.add_argument( + "--version", + action="version", + version=( + f"{version} ({plugin_versions}) " + f"{utils.get_python_version()}" + ), + ) + self.parser.add_argument("filenames", nargs="*", metavar="filename") + + self.config_options_dict: dict[str, Option] = {} + self.options: list[Option] = [] + self.extended_default_ignore: list[str] = [] + self.extended_default_select: list[str] = [] + + self._current_group: argparse._ArgumentGroup | None = None + + # TODO: maybe make this a free function to reduce api surface area + def register_plugins(self, plugins: Plugins) -> None: + """Register the plugin options (if needed).""" + groups: dict[str, argparse._ArgumentGroup] = {} + + def _set_group(name: str) -> None: + try: + self._current_group = groups[name] + except KeyError: + group = self.parser.add_argument_group(name) + self._current_group = groups[name] = group + + for loaded in plugins.all_plugins(): + add_options = getattr(loaded.obj, "add_options", None) + if add_options: + _set_group(loaded.plugin.package) + add_options(self) + + if loaded.plugin.entry_point.group == "flake8.extension": + self.extend_default_select([loaded.entry_name]) + + # isn't strictly necessary, but seems cleaner + self._current_group = None + + def add_option(self, *args: Any, **kwargs: Any) -> None: + """Create and register a new option. + + See parameters for :class:`~flake8.options.manager.Option` for + acceptable arguments to this method. + + .. note:: + + ``short_option_name`` and ``long_option_name`` may be specified + positionally as they are with argparse normally. + """ + option = Option(*args, **kwargs) + option_args, option_kwargs = option.to_argparse() + if self._current_group is not None: + self._current_group.add_argument(*option_args, **option_kwargs) + else: + self.parser.add_argument(*option_args, **option_kwargs) + self.options.append(option) + if option.parse_from_config: + name = option.config_name + assert name is not None + self.config_options_dict[name] = option + self.config_options_dict[name.replace("_", "-")] = option + LOG.debug('Registered option "%s".', option) + + def extend_default_ignore(self, error_codes: Sequence[str]) -> None: + """Extend the default ignore list with the error codes provided. + + :param error_codes: + List of strings that are the error/warning codes with which to + extend the default ignore list. + """ + LOG.debug("Extending default ignore list with %r", error_codes) + self.extended_default_ignore.extend(error_codes) + + def extend_default_select(self, error_codes: Sequence[str]) -> None: + """Extend the default select list with the error codes provided. + + :param error_codes: + List of strings that are the error/warning codes with which + to extend the default select list. + """ + LOG.debug("Extending default select list with %r", error_codes) + self.extended_default_select.extend(error_codes) + + def parse_args( + self, + args: Sequence[str] | None = None, + values: argparse.Namespace | None = None, + ) -> argparse.Namespace: + """Proxy to calling the OptionParser's parse_args method.""" + if values: + self.parser.set_defaults(**vars(values)) + return self.parser.parse_args(args) diff --git a/py311/lib/python3.11/site-packages/flake8/options/parse_args.py b/py311/lib/python3.11/site-packages/flake8/options/parse_args.py new file mode 100644 index 0000000000000000000000000000000000000000..ff5e08f49038af7d4ce0151a624437f9586ef1cd --- /dev/null +++ b/py311/lib/python3.11/site-packages/flake8/options/parse_args.py @@ -0,0 +1,70 @@ +"""Procedure for parsing args, config, loading plugins.""" +from __future__ import annotations + +import argparse +from collections.abc import Sequence + +import flake8 +from flake8.main import options +from flake8.options import aggregator +from flake8.options import config +from flake8.options import manager +from flake8.plugins import finder + + +def parse_args( + argv: Sequence[str], +) -> tuple[finder.Plugins, argparse.Namespace]: + """Procedure for parsing args, config, loading plugins.""" + prelim_parser = options.stage1_arg_parser() + + args0, rest = prelim_parser.parse_known_args(argv) + # XXX (ericvw): Special case "forwarding" the output file option so + # that it can be reparsed again for the BaseFormatter.filename. + if args0.output_file: + rest.extend(("--output-file", args0.output_file)) + + flake8.configure_logging(args0.verbose, args0.output_file) + + cfg, cfg_dir = config.load_config( + config=args0.config, + extra=args0.append_config, + isolated=args0.isolated, + ) + + plugin_opts = finder.parse_plugin_options( + cfg, + cfg_dir, + enable_extensions=args0.enable_extensions, + require_plugins=args0.require_plugins, + ) + raw_plugins = finder.find_plugins(cfg, plugin_opts) + plugins = finder.load_plugins(raw_plugins, plugin_opts) + + option_manager = manager.OptionManager( + version=flake8.__version__, + plugin_versions=plugins.versions_str(), + parents=[prelim_parser], + formatter_names=list(plugins.reporters), + ) + options.register_default_options(option_manager) + option_manager.register_plugins(plugins) + + opts = aggregator.aggregate_options(option_manager, cfg, cfg_dir, rest) + + for loaded in plugins.all_plugins(): + parse_options = getattr(loaded.obj, "parse_options", None) + if parse_options is None: + continue + + # XXX: ideally we wouldn't have two forms of parse_options + try: + parse_options( + option_manager, + opts, + opts.filenames, + ) + except TypeError: + parse_options(opts) + + return plugins, opts diff --git a/py311/lib/python3.11/site-packages/flake8/plugins/__init__.py b/py311/lib/python3.11/site-packages/flake8/plugins/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..b540313378be4ffeb2b4b89abf0ae0160c4efbdc --- /dev/null +++ b/py311/lib/python3.11/site-packages/flake8/plugins/__init__.py @@ -0,0 +1,2 @@ +"""Submodule of built-in plugins and plugin managers.""" +from __future__ import annotations diff --git a/py311/lib/python3.11/site-packages/flake8/plugins/finder.py b/py311/lib/python3.11/site-packages/flake8/plugins/finder.py new file mode 100644 index 0000000000000000000000000000000000000000..88b66a08af631ebf523e1e69e76abd1c4bb7b29c --- /dev/null +++ b/py311/lib/python3.11/site-packages/flake8/plugins/finder.py @@ -0,0 +1,365 @@ +"""Functions related to finding and loading plugins.""" +from __future__ import annotations + +import configparser +import importlib.metadata +import inspect +import itertools +import logging +import sys +from collections.abc import Generator +from collections.abc import Iterable +from typing import Any +from typing import NamedTuple + +from flake8 import utils +from flake8.defaults import VALID_CODE_PREFIX +from flake8.exceptions import ExecutionError +from flake8.exceptions import FailedToLoadPlugin + +LOG = logging.getLogger(__name__) + +FLAKE8_GROUPS = frozenset(("flake8.extension", "flake8.report")) + +BANNED_PLUGINS = { + "flake8-colors": "5.0", + "flake8-per-file-ignores": "3.7", +} + + +class Plugin(NamedTuple): + """A plugin before loading.""" + + package: str + version: str + entry_point: importlib.metadata.EntryPoint + + +class LoadedPlugin(NamedTuple): + """Represents a plugin after being imported.""" + + plugin: Plugin + obj: Any + parameters: dict[str, bool] + + @property + def entry_name(self) -> str: + """Return the name given in the packaging metadata.""" + return self.plugin.entry_point.name + + @property + def display_name(self) -> str: + """Return the name for use in user-facing / error messages.""" + return f"{self.plugin.package}[{self.entry_name}]" + + +class Checkers(NamedTuple): + """Classified plugins needed for checking.""" + + tree: list[LoadedPlugin] + logical_line: list[LoadedPlugin] + physical_line: list[LoadedPlugin] + + +class Plugins(NamedTuple): + """Classified plugins.""" + + checkers: Checkers + reporters: dict[str, LoadedPlugin] + disabled: list[LoadedPlugin] + + def all_plugins(self) -> Generator[LoadedPlugin]: + """Return an iterator over all :class:`LoadedPlugin`s.""" + yield from self.checkers.tree + yield from self.checkers.logical_line + yield from self.checkers.physical_line + yield from self.reporters.values() + + def versions_str(self) -> str: + """Return a user-displayed list of plugin versions.""" + return ", ".join( + sorted( + { + f"{loaded.plugin.package}: {loaded.plugin.version}" + for loaded in self.all_plugins() + if loaded.plugin.package not in {"flake8", "local"} + } + ) + ) + + +class PluginOptions(NamedTuple): + """Options related to plugin loading.""" + + local_plugin_paths: tuple[str, ...] + enable_extensions: frozenset[str] + require_plugins: frozenset[str] + + @classmethod + def blank(cls) -> PluginOptions: + """Make a blank PluginOptions, mostly used for tests.""" + return cls( + local_plugin_paths=(), + enable_extensions=frozenset(), + require_plugins=frozenset(), + ) + + +def _parse_option( + cfg: configparser.RawConfigParser, + cfg_opt_name: str, + opt: str | None, +) -> list[str]: + # specified on commandline: use that + if opt is not None: + return utils.parse_comma_separated_list(opt) + else: + # ideally this would reuse our config parsing framework but we need to + # parse this from preliminary options before plugins are enabled + for opt_name in (cfg_opt_name, cfg_opt_name.replace("_", "-")): + val = cfg.get("flake8", opt_name, fallback=None) + if val is not None: + return utils.parse_comma_separated_list(val) + else: + return [] + + +def parse_plugin_options( + cfg: configparser.RawConfigParser, + cfg_dir: str, + *, + enable_extensions: str | None, + require_plugins: str | None, +) -> PluginOptions: + """Parse plugin loading related options.""" + paths_s = cfg.get("flake8:local-plugins", "paths", fallback="").strip() + paths = utils.parse_comma_separated_list(paths_s) + paths = utils.normalize_paths(paths, cfg_dir) + + return PluginOptions( + local_plugin_paths=tuple(paths), + enable_extensions=frozenset( + _parse_option(cfg, "enable_extensions", enable_extensions), + ), + require_plugins=frozenset( + _parse_option(cfg, "require_plugins", require_plugins), + ), + ) + + +def _flake8_plugins( + eps: Iterable[importlib.metadata.EntryPoint], + name: str, + version: str, +) -> Generator[Plugin]: + pyflakes_meta = importlib.metadata.distribution("pyflakes").metadata + pycodestyle_meta = importlib.metadata.distribution("pycodestyle").metadata + + for ep in eps: + if ep.group not in FLAKE8_GROUPS: + continue + + if ep.name == "F": + yield Plugin(pyflakes_meta["name"], pyflakes_meta["version"], ep) + elif ep.name in "EW": + # pycodestyle provides both `E` and `W` -- but our default select + # handles those + # ideally pycodestyle's plugin entrypoints would exactly represent + # the codes they produce... + yield Plugin( + pycodestyle_meta["name"], pycodestyle_meta["version"], ep + ) + else: + yield Plugin(name, version, ep) + + +def _find_importlib_plugins() -> Generator[Plugin]: + # some misconfigured pythons (RHEL) have things on `sys.path` twice + seen = set() + for dist in importlib.metadata.distributions(): + # assigned to prevent continual reparsing + eps = dist.entry_points + + # perf: skip parsing `.metadata` (slow) if no entry points match + if not any(ep.group in FLAKE8_GROUPS for ep in eps): + continue + + # assigned to prevent continual reparsing + meta = dist.metadata + + if meta["name"] in seen: + continue + else: + seen.add(meta["name"]) + + if meta["name"] in BANNED_PLUGINS: + LOG.warning( + "%s plugin is obsolete in flake8>=%s", + meta["name"], + BANNED_PLUGINS[meta["name"]], + ) + continue + elif meta["name"] == "flake8": + # special case flake8 which provides plugins for pyflakes / + # pycodestyle + yield from _flake8_plugins(eps, meta["name"], meta["version"]) + continue + + for ep in eps: + if ep.group in FLAKE8_GROUPS: + yield Plugin(meta["name"], meta["version"], ep) + + +def _find_local_plugins( + cfg: configparser.RawConfigParser, +) -> Generator[Plugin]: + for plugin_type in ("extension", "report"): + group = f"flake8.{plugin_type}" + for plugin_s in utils.parse_comma_separated_list( + cfg.get("flake8:local-plugins", plugin_type, fallback="").strip(), + regexp=utils.LOCAL_PLUGIN_LIST_RE, + ): + name, _, entry_str = plugin_s.partition("=") + name, entry_str = name.strip(), entry_str.strip() + ep = importlib.metadata.EntryPoint(name, entry_str, group) + yield Plugin("local", "local", ep) + + +def _check_required_plugins( + plugins: list[Plugin], + expected: frozenset[str], +) -> None: + plugin_names = { + utils.normalize_pypi_name(plugin.package) for plugin in plugins + } + expected_names = {utils.normalize_pypi_name(name) for name in expected} + missing_plugins = expected_names - plugin_names + + if missing_plugins: + raise ExecutionError( + f"required plugins were not installed!\n" + f"- installed: {', '.join(sorted(plugin_names))}\n" + f"- expected: {', '.join(sorted(expected_names))}\n" + f"- missing: {', '.join(sorted(missing_plugins))}" + ) + + +def find_plugins( + cfg: configparser.RawConfigParser, + opts: PluginOptions, +) -> list[Plugin]: + """Discovers all plugins (but does not load them).""" + ret = [*_find_importlib_plugins(), *_find_local_plugins(cfg)] + + # for determinism, sort the list + ret.sort() + + _check_required_plugins(ret, opts.require_plugins) + + return ret + + +def _parameters_for(func: Any) -> dict[str, bool]: + """Return the parameters for the plugin. + + This will inspect the plugin and return either the function parameters + if the plugin is a function or the parameters for ``__init__`` after + ``self`` if the plugin is a class. + + :returns: + A dictionary mapping the parameter name to whether or not it is + required (a.k.a., is positional only/does not have a default). + """ + is_class = not inspect.isfunction(func) + if is_class: + func = func.__init__ + + parameters = { + parameter.name: parameter.default is inspect.Parameter.empty + for parameter in inspect.signature(func).parameters.values() + if parameter.kind is inspect.Parameter.POSITIONAL_OR_KEYWORD + } + + if is_class: + parameters.pop("self", None) + + return parameters + + +def _load_plugin(plugin: Plugin) -> LoadedPlugin: + try: + obj = plugin.entry_point.load() + except Exception as e: + raise FailedToLoadPlugin(plugin.package, e) + + if not callable(obj): + err = TypeError("expected loaded plugin to be callable") + raise FailedToLoadPlugin(plugin.package, err) + + return LoadedPlugin(plugin, obj, _parameters_for(obj)) + + +def _import_plugins( + plugins: list[Plugin], + opts: PluginOptions, +) -> list[LoadedPlugin]: + sys.path.extend(opts.local_plugin_paths) + return [_load_plugin(p) for p in plugins] + + +def _classify_plugins( + plugins: list[LoadedPlugin], + opts: PluginOptions, +) -> Plugins: + tree = [] + logical_line = [] + physical_line = [] + reporters = {} + disabled = [] + + for loaded in plugins: + if ( + getattr(loaded.obj, "off_by_default", False) + and loaded.plugin.entry_point.name not in opts.enable_extensions + ): + disabled.append(loaded) + elif loaded.plugin.entry_point.group == "flake8.report": + reporters[loaded.entry_name] = loaded + elif "tree" in loaded.parameters: + tree.append(loaded) + elif "logical_line" in loaded.parameters: + logical_line.append(loaded) + elif "physical_line" in loaded.parameters: + physical_line.append(loaded) + else: + raise NotImplementedError(f"what plugin type? {loaded}") + + for loaded in itertools.chain(tree, logical_line, physical_line): + if not VALID_CODE_PREFIX.match(loaded.entry_name): + raise ExecutionError( + f"plugin code for `{loaded.display_name}` does not match " + f"{VALID_CODE_PREFIX.pattern}" + ) + + return Plugins( + checkers=Checkers( + tree=tree, + logical_line=logical_line, + physical_line=physical_line, + ), + reporters=reporters, + disabled=disabled, + ) + + +def load_plugins( + plugins: list[Plugin], + opts: PluginOptions, +) -> Plugins: + """Load and classify all flake8 plugins. + + - first: extends ``sys.path`` with ``paths`` (to import local plugins) + - next: converts the ``Plugin``s to ``LoadedPlugins`` + - finally: classifies plugins into their specific types + """ + return _classify_plugins(_import_plugins(plugins, opts), opts) diff --git a/py311/lib/python3.11/site-packages/flake8/plugins/pycodestyle.py b/py311/lib/python3.11/site-packages/flake8/plugins/pycodestyle.py new file mode 100644 index 0000000000000000000000000000000000000000..cd760dc6c828dbf0f5f8285a6ef11b34e858db79 --- /dev/null +++ b/py311/lib/python3.11/site-packages/flake8/plugins/pycodestyle.py @@ -0,0 +1,112 @@ +"""Generated using ./bin/gen-pycodestyle-plugin.""" +# fmt: off +from __future__ import annotations + +from collections.abc import Generator +from typing import Any + +from pycodestyle import ambiguous_identifier as _ambiguous_identifier +from pycodestyle import bare_except as _bare_except +from pycodestyle import blank_lines as _blank_lines +from pycodestyle import break_after_binary_operator as _break_after_binary_operator # noqa: E501 +from pycodestyle import break_before_binary_operator as _break_before_binary_operator # noqa: E501 +from pycodestyle import comparison_negative as _comparison_negative +from pycodestyle import comparison_to_singleton as _comparison_to_singleton +from pycodestyle import comparison_type as _comparison_type +from pycodestyle import compound_statements as _compound_statements +from pycodestyle import continued_indentation as _continued_indentation +from pycodestyle import explicit_line_join as _explicit_line_join +from pycodestyle import extraneous_whitespace as _extraneous_whitespace +from pycodestyle import imports_on_separate_lines as _imports_on_separate_lines +from pycodestyle import indentation as _indentation +from pycodestyle import maximum_doc_length as _maximum_doc_length +from pycodestyle import maximum_line_length as _maximum_line_length +from pycodestyle import missing_whitespace as _missing_whitespace +from pycodestyle import missing_whitespace_after_keyword as _missing_whitespace_after_keyword # noqa: E501 +from pycodestyle import module_imports_on_top_of_file as _module_imports_on_top_of_file # noqa: E501 +from pycodestyle import python_3000_invalid_escape_sequence as _python_3000_invalid_escape_sequence # noqa: E501 +from pycodestyle import tabs_obsolete as _tabs_obsolete +from pycodestyle import tabs_or_spaces as _tabs_or_spaces +from pycodestyle import trailing_blank_lines as _trailing_blank_lines +from pycodestyle import trailing_whitespace as _trailing_whitespace +from pycodestyle import whitespace_around_comma as _whitespace_around_comma +from pycodestyle import whitespace_around_keywords as _whitespace_around_keywords # noqa: E501 +from pycodestyle import whitespace_around_named_parameter_equals as _whitespace_around_named_parameter_equals # noqa: E501 +from pycodestyle import whitespace_around_operator as _whitespace_around_operator # noqa: E501 +from pycodestyle import whitespace_before_comment as _whitespace_before_comment +from pycodestyle import whitespace_before_parameters as _whitespace_before_parameters # noqa: E501 + + +def pycodestyle_logical( + blank_before: Any, + blank_lines: Any, + checker_state: Any, + hang_closing: Any, + indent_char: Any, + indent_level: Any, + indent_size: Any, + line_number: Any, + lines: Any, + logical_line: Any, + max_doc_length: Any, + noqa: Any, + previous_indent_level: Any, + previous_logical: Any, + previous_unindented_logical_line: Any, + tokens: Any, + verbose: Any, +) -> Generator[tuple[int, str]]: + """Run pycodestyle logical checks.""" + yield from _ambiguous_identifier(logical_line, tokens) + yield from _bare_except(logical_line, noqa) + yield from _blank_lines(logical_line, blank_lines, indent_level, line_number, blank_before, previous_logical, previous_unindented_logical_line, previous_indent_level, lines) # noqa: E501 + yield from _break_after_binary_operator(logical_line, tokens) + yield from _break_before_binary_operator(logical_line, tokens) + yield from _comparison_negative(logical_line) + yield from _comparison_to_singleton(logical_line, noqa) + yield from _comparison_type(logical_line, noqa) + yield from _compound_statements(logical_line) + yield from _continued_indentation(logical_line, tokens, indent_level, hang_closing, indent_char, indent_size, noqa, verbose) # noqa: E501 + yield from _explicit_line_join(logical_line, tokens) + yield from _extraneous_whitespace(logical_line) + yield from _imports_on_separate_lines(logical_line) + yield from _indentation(logical_line, previous_logical, indent_char, indent_level, previous_indent_level, indent_size) # noqa: E501 + yield from _maximum_doc_length(logical_line, max_doc_length, noqa, tokens) + yield from _missing_whitespace(logical_line, tokens) + yield from _missing_whitespace_after_keyword(logical_line, tokens) + yield from _module_imports_on_top_of_file(logical_line, indent_level, checker_state, noqa) # noqa: E501 + yield from _python_3000_invalid_escape_sequence(logical_line, tokens, noqa) + yield from _whitespace_around_comma(logical_line) + yield from _whitespace_around_keywords(logical_line) + yield from _whitespace_around_named_parameter_equals(logical_line, tokens) + yield from _whitespace_around_operator(logical_line) + yield from _whitespace_before_comment(logical_line, tokens) + yield from _whitespace_before_parameters(logical_line, tokens) + + +def pycodestyle_physical( + indent_char: Any, + line_number: Any, + lines: Any, + max_line_length: Any, + multiline: Any, + noqa: Any, + physical_line: Any, + total_lines: Any, +) -> Generator[tuple[int, str]]: + """Run pycodestyle physical checks.""" + ret = _maximum_line_length(physical_line, max_line_length, multiline, line_number, noqa) # noqa: E501 + if ret is not None: + yield ret + ret = _tabs_obsolete(physical_line) + if ret is not None: + yield ret + ret = _tabs_or_spaces(physical_line, indent_char) + if ret is not None: + yield ret + ret = _trailing_blank_lines(physical_line, lines, line_number, total_lines) + if ret is not None: + yield ret + ret = _trailing_whitespace(physical_line) + if ret is not None: + yield ret diff --git a/py311/lib/python3.11/site-packages/flake8/plugins/pyflakes.py b/py311/lib/python3.11/site-packages/flake8/plugins/pyflakes.py new file mode 100644 index 0000000000000000000000000000000000000000..66d8c1cdafb0255a772de57b2fd9bd5a3d6e7f6d --- /dev/null +++ b/py311/lib/python3.11/site-packages/flake8/plugins/pyflakes.py @@ -0,0 +1,114 @@ +"""Plugin built-in to Flake8 to treat pyflakes as a plugin.""" +from __future__ import annotations + +import argparse +import ast +import logging +from collections.abc import Generator +from typing import Any + +import pyflakes.checker + +from flake8.options.manager import OptionManager + +LOG = logging.getLogger(__name__) + +FLAKE8_PYFLAKES_CODES = { + "UnusedImport": "F401", + "ImportShadowedByLoopVar": "F402", + "ImportStarUsed": "F403", + "LateFutureImport": "F404", + "ImportStarUsage": "F405", + "ImportStarNotPermitted": "F406", + "FutureFeatureNotDefined": "F407", + "PercentFormatInvalidFormat": "F501", + "PercentFormatExpectedMapping": "F502", + "PercentFormatExpectedSequence": "F503", + "PercentFormatExtraNamedArguments": "F504", + "PercentFormatMissingArgument": "F505", + "PercentFormatMixedPositionalAndNamed": "F506", + "PercentFormatPositionalCountMismatch": "F507", + "PercentFormatStarRequiresSequence": "F508", + "PercentFormatUnsupportedFormatCharacter": "F509", + "StringDotFormatInvalidFormat": "F521", + "StringDotFormatExtraNamedArguments": "F522", + "StringDotFormatExtraPositionalArguments": "F523", + "StringDotFormatMissingArgument": "F524", + "StringDotFormatMixingAutomatic": "F525", + "FStringMissingPlaceholders": "F541", + "TStringMissingPlaceholders": "F542", + "MultiValueRepeatedKeyLiteral": "F601", + "MultiValueRepeatedKeyVariable": "F602", + "TooManyExpressionsInStarredAssignment": "F621", + "TwoStarredExpressions": "F622", + "AssertTuple": "F631", + "IsLiteral": "F632", + "InvalidPrintSyntax": "F633", + "IfTuple": "F634", + "BreakOutsideLoop": "F701", + "ContinueOutsideLoop": "F702", + "YieldOutsideFunction": "F704", + "ReturnOutsideFunction": "F706", + "DefaultExceptNotLast": "F707", + "DoctestSyntaxError": "F721", + "ForwardAnnotationSyntaxError": "F722", + "RedefinedWhileUnused": "F811", + "UndefinedName": "F821", + "UndefinedExport": "F822", + "UndefinedLocal": "F823", + "UnusedIndirectAssignment": "F824", + "DuplicateArgument": "F831", + "UnusedVariable": "F841", + "UnusedAnnotation": "F842", + "RaiseNotImplemented": "F901", +} + + +class FlakesChecker(pyflakes.checker.Checker): + """Subclass the Pyflakes checker to conform with the flake8 API.""" + + with_doctest = False + + def __init__(self, tree: ast.AST, filename: str) -> None: + """Initialize the PyFlakes plugin with an AST tree and filename.""" + super().__init__( + tree, filename=filename, withDoctest=self.with_doctest + ) + + @classmethod + def add_options(cls, parser: OptionManager) -> None: + """Register options for PyFlakes on the Flake8 OptionManager.""" + parser.add_option( + "--builtins", + parse_from_config=True, + comma_separated_list=True, + help="define more built-ins, comma separated", + ) + parser.add_option( + "--doctests", + default=False, + action="store_true", + parse_from_config=True, + help="also check syntax of the doctests", + ) + + @classmethod + def parse_options(cls, options: argparse.Namespace) -> None: + """Parse option values from Flake8's OptionManager.""" + if options.builtins: + cls.builtIns = cls.builtIns.union(options.builtins) + cls.with_doctest = options.doctests + + def run(self) -> Generator[tuple[int, int, str, type[Any]]]: + """Run the plugin.""" + for message in self.messages: + col = getattr(message, "col", 0) + yield ( + message.lineno, + col, + "{} {}".format( + FLAKE8_PYFLAKES_CODES.get(type(message).__name__, "F999"), + message.message % message.message_args, + ), + message.__class__, + ) diff --git a/py311/lib/python3.11/site-packages/flake8/plugins/reporter.py b/py311/lib/python3.11/site-packages/flake8/plugins/reporter.py new file mode 100644 index 0000000000000000000000000000000000000000..a5749c03cb1cfa3fc6b727e0ddb18c5437598662 --- /dev/null +++ b/py311/lib/python3.11/site-packages/flake8/plugins/reporter.py @@ -0,0 +1,42 @@ +"""Functions for constructing the requested report plugin.""" +from __future__ import annotations + +import argparse +import logging + +from flake8.formatting.base import BaseFormatter +from flake8.plugins.finder import LoadedPlugin + +LOG = logging.getLogger(__name__) + + +def make( + reporters: dict[str, LoadedPlugin], + options: argparse.Namespace, +) -> BaseFormatter: + """Make the formatter from the requested user options. + + - if :option:`flake8 --quiet` is specified, return the ``quiet-filename`` + formatter. + - if :option:`flake8 --quiet` is specified at least twice, return the + ``quiet-nothing`` formatter. + - otherwise attempt to return the formatter by name. + - failing that, assume it is a format string and return the ``default`` + formatter. + """ + format_name = options.format + if options.quiet == 1: + format_name = "quiet-filename" + elif options.quiet >= 2: + format_name = "quiet-nothing" + + try: + format_plugin = reporters[format_name] + except KeyError: + LOG.warning( + "%r is an unknown formatter. Falling back to default.", + format_name, + ) + format_plugin = reporters["default"] + + return format_plugin.obj(options) diff --git a/py311/lib/python3.11/site-packages/fontTools/cu2qu/cu2qu.cpython-311-x86_64-linux-gnu.so b/py311/lib/python3.11/site-packages/fontTools/cu2qu/cu2qu.cpython-311-x86_64-linux-gnu.so new file mode 100644 index 0000000000000000000000000000000000000000..af492182911e8d06f26c3b3dbf0c97ff61df0f4d --- /dev/null +++ b/py311/lib/python3.11/site-packages/fontTools/cu2qu/cu2qu.cpython-311-x86_64-linux-gnu.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:29f8d6056801d7cf48081f8de3dadc7c0fe2037ad1fc1e63f43ee0e052ed94c1 +size 1107736 diff --git a/py311/lib/python3.11/site-packages/fontTools/feaLib/lexer.cpython-311-x86_64-linux-gnu.so b/py311/lib/python3.11/site-packages/fontTools/feaLib/lexer.cpython-311-x86_64-linux-gnu.so new file mode 100644 index 0000000000000000000000000000000000000000..b6c100438d2148c88181a5cc33760b6c59328224 --- /dev/null +++ b/py311/lib/python3.11/site-packages/fontTools/feaLib/lexer.cpython-311-x86_64-linux-gnu.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4abc1f01de1f61a482e76a0b69bfab3d84af6bd9ee7055f9a1675bd57852b98b +size 1512184 diff --git a/py311/lib/python3.11/site-packages/fontTools/misc/bezierTools.cpython-311-x86_64-linux-gnu.so b/py311/lib/python3.11/site-packages/fontTools/misc/bezierTools.cpython-311-x86_64-linux-gnu.so new file mode 100644 index 0000000000000000000000000000000000000000..da51ca9f10c76ec6e725f96f91df739d407f4305 --- /dev/null +++ b/py311/lib/python3.11/site-packages/fontTools/misc/bezierTools.cpython-311-x86_64-linux-gnu.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c3bcd2d5dc4e52ebff9dafa759757e3f3ca7add4a686c1a78fda2ea8816ec295 +size 5095248 diff --git a/py311/lib/python3.11/site-packages/fontTools/pens/momentsPen.cpython-311-x86_64-linux-gnu.so b/py311/lib/python3.11/site-packages/fontTools/pens/momentsPen.cpython-311-x86_64-linux-gnu.so new file mode 100644 index 0000000000000000000000000000000000000000..717daecc97f4139d62b20e08efcbd20c1f7edaa8 --- /dev/null +++ b/py311/lib/python3.11/site-packages/fontTools/pens/momentsPen.cpython-311-x86_64-linux-gnu.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:076c96f9eac462139c9528b2c507f8ae32a60284db634ce57da9a7cbc9027af4 +size 926496 diff --git a/py311/lib/python3.11/site-packages/fontTools/qu2cu/qu2cu.cpython-311-x86_64-linux-gnu.so b/py311/lib/python3.11/site-packages/fontTools/qu2cu/qu2cu.cpython-311-x86_64-linux-gnu.so new file mode 100644 index 0000000000000000000000000000000000000000..8a1d73652c42c3919905f8d9723fa8c2a99d6c5f --- /dev/null +++ b/py311/lib/python3.11/site-packages/fontTools/qu2cu/qu2cu.cpython-311-x86_64-linux-gnu.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a5d84aed7b45861667dde6eaa62fb54786a92f2c21d7fe0a19da47c647270e7c +size 1248624 diff --git a/py311/lib/python3.11/site-packages/fontTools/varLib/iup.cpython-311-x86_64-linux-gnu.so b/py311/lib/python3.11/site-packages/fontTools/varLib/iup.cpython-311-x86_64-linux-gnu.so new file mode 100644 index 0000000000000000000000000000000000000000..01c1536b010c55e29592ab5b0e17f79e96f0391e --- /dev/null +++ b/py311/lib/python3.11/site-packages/fontTools/varLib/iup.cpython-311-x86_64-linux-gnu.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:074f3c99c4282bd06e2a1b1a2e177af70a24fb883552cf9f8bef754d7eb9b24a +size 1685552 diff --git a/py311/lib/python3.11/site-packages/frozenlist/_frozenlist.cpython-311-x86_64-linux-gnu.so b/py311/lib/python3.11/site-packages/frozenlist/_frozenlist.cpython-311-x86_64-linux-gnu.so new file mode 100644 index 0000000000000000000000000000000000000000..a8fddc909c3dd632d9a70678ac9dc681ce8edf38 --- /dev/null +++ b/py311/lib/python3.11/site-packages/frozenlist/_frozenlist.cpython-311-x86_64-linux-gnu.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:993cc0a84aab0ffec074a4958add6841b19f155b74d8a85dbfec86558db80181 +size 748000 diff --git a/py311/lib/python3.11/site-packages/google_generativeai-0.8.6-py3.13-nspkg.pth b/py311/lib/python3.11/site-packages/google_generativeai-0.8.6-py3.13-nspkg.pth new file mode 100644 index 0000000000000000000000000000000000000000..e01f0bedec6e7bb009ca58b678fb86bc3cd37e3c --- /dev/null +++ b/py311/lib/python3.11/site-packages/google_generativeai-0.8.6-py3.13-nspkg.pth @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:492187369ed89466a43eca61c645a6d1f2a0de9ceb7d0611438d993ed88f17ee +size 467 diff --git a/py311/lib/python3.11/site-packages/grpc/_cython/cygrpc.cpython-311-x86_64-linux-gnu.so b/py311/lib/python3.11/site-packages/grpc/_cython/cygrpc.cpython-311-x86_64-linux-gnu.so new file mode 100644 index 0000000000000000000000000000000000000000..26e2a7a0653c61f41f7e892f23099ac931d86e36 --- /dev/null +++ b/py311/lib/python3.11/site-packages/grpc/_cython/cygrpc.cpython-311-x86_64-linux-gnu.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e60b88e88fe4fa49ab97739b2dc7ad3f3369868d41d7d9bcfa1ed01adfd12638 +size 15451416 diff --git a/py311/lib/python3.11/site-packages/httpcore-1.0.9.dist-info/licenses/LICENSE.md b/py311/lib/python3.11/site-packages/httpcore-1.0.9.dist-info/licenses/LICENSE.md new file mode 100644 index 0000000000000000000000000000000000000000..311b2b56c53f678ab95fc0def708c675d521a807 --- /dev/null +++ b/py311/lib/python3.11/site-packages/httpcore-1.0.9.dist-info/licenses/LICENSE.md @@ -0,0 +1,27 @@ +Copyright © 2020, [Encode OSS Ltd](https://www.encode.io/). +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/py311/lib/python3.11/site-packages/httplib2/__pycache__/__init__.cpython-311.pyc b/py311/lib/python3.11/site-packages/httplib2/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7a7388075411333f676ca6007b4d3f14837f0590 Binary files /dev/null and b/py311/lib/python3.11/site-packages/httplib2/__pycache__/__init__.cpython-311.pyc differ diff --git a/py311/lib/python3.11/site-packages/httplib2/__pycache__/auth.cpython-311.pyc b/py311/lib/python3.11/site-packages/httplib2/__pycache__/auth.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8f84ce81f6fb68dc5b728c4dfc6c6fbbbc21c038 Binary files /dev/null and b/py311/lib/python3.11/site-packages/httplib2/__pycache__/auth.cpython-311.pyc differ diff --git a/py311/lib/python3.11/site-packages/httplib2/__pycache__/certs.cpython-311.pyc b/py311/lib/python3.11/site-packages/httplib2/__pycache__/certs.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fa112a771ea71ecb561e36d427d5fa7fb8a228d9 Binary files /dev/null and b/py311/lib/python3.11/site-packages/httplib2/__pycache__/certs.cpython-311.pyc differ diff --git a/py311/lib/python3.11/site-packages/httplib2/__pycache__/error.cpython-311.pyc b/py311/lib/python3.11/site-packages/httplib2/__pycache__/error.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0c2eb09349e91c9f73fa1e48281801bfa9edc280 Binary files /dev/null and b/py311/lib/python3.11/site-packages/httplib2/__pycache__/error.cpython-311.pyc differ diff --git a/py311/lib/python3.11/site-packages/httplib2/__pycache__/iri2uri.cpython-311.pyc b/py311/lib/python3.11/site-packages/httplib2/__pycache__/iri2uri.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b4a74a4aa91fd64abf9f3d1c70ff03d1fabfff29 Binary files /dev/null and b/py311/lib/python3.11/site-packages/httplib2/__pycache__/iri2uri.cpython-311.pyc differ diff --git a/py311/lib/python3.11/site-packages/hydra/__pycache__/__init__.cpython-311.pyc b/py311/lib/python3.11/site-packages/hydra/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..437f0cd25d8971704854e3a543dbb67c67a14c39 Binary files /dev/null and b/py311/lib/python3.11/site-packages/hydra/__pycache__/__init__.cpython-311.pyc differ diff --git a/py311/lib/python3.11/site-packages/hydra/__pycache__/compose.cpython-311.pyc b/py311/lib/python3.11/site-packages/hydra/__pycache__/compose.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b74475a074651addbce45070557a5ca67d638914 Binary files /dev/null and b/py311/lib/python3.11/site-packages/hydra/__pycache__/compose.cpython-311.pyc differ diff --git a/py311/lib/python3.11/site-packages/hydra/__pycache__/errors.cpython-311.pyc b/py311/lib/python3.11/site-packages/hydra/__pycache__/errors.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..615bf1dc97ea5448954cb83e09c908d0b264f228 Binary files /dev/null and b/py311/lib/python3.11/site-packages/hydra/__pycache__/errors.cpython-311.pyc differ diff --git a/py311/lib/python3.11/site-packages/hydra/__pycache__/initialize.cpython-311.pyc b/py311/lib/python3.11/site-packages/hydra/__pycache__/initialize.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5d34103fb70a3acf725a827c5936f6e2a6c13e01 Binary files /dev/null and b/py311/lib/python3.11/site-packages/hydra/__pycache__/initialize.cpython-311.pyc differ diff --git a/py311/lib/python3.11/site-packages/hydra/__pycache__/main.cpython-311.pyc b/py311/lib/python3.11/site-packages/hydra/__pycache__/main.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..16cbc9e07698d8910e87b09f1979f1d863339051 Binary files /dev/null and b/py311/lib/python3.11/site-packages/hydra/__pycache__/main.cpython-311.pyc differ diff --git a/py311/lib/python3.11/site-packages/hydra/__pycache__/types.cpython-311.pyc b/py311/lib/python3.11/site-packages/hydra/__pycache__/types.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..85617c6ecc55ab0bc8ae96c6c608c01acb84581e Binary files /dev/null and b/py311/lib/python3.11/site-packages/hydra/__pycache__/types.cpython-311.pyc differ diff --git a/py311/lib/python3.11/site-packages/hydra/__pycache__/utils.cpython-311.pyc b/py311/lib/python3.11/site-packages/hydra/__pycache__/utils.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5f26d9ceb2ed620088095a59a177cbd2061bf0b2 Binary files /dev/null and b/py311/lib/python3.11/site-packages/hydra/__pycache__/utils.cpython-311.pyc differ diff --git a/py311/lib/python3.11/site-packages/hydra/__pycache__/version.cpython-311.pyc b/py311/lib/python3.11/site-packages/hydra/__pycache__/version.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4b79ba03e94c26b0dbae04cae8a63b17a9c47d0c Binary files /dev/null and b/py311/lib/python3.11/site-packages/hydra/__pycache__/version.cpython-311.pyc differ diff --git a/py311/lib/python3.11/site-packages/hydra/_internal/__init__.py b/py311/lib/python3.11/site-packages/hydra/_internal/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..168f9979a4623806934b0ff1102ac166704e7dec --- /dev/null +++ b/py311/lib/python3.11/site-packages/hydra/_internal/__init__.py @@ -0,0 +1 @@ +# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved diff --git a/py311/lib/python3.11/site-packages/hydra/_internal/__pycache__/__init__.cpython-311.pyc b/py311/lib/python3.11/site-packages/hydra/_internal/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e8ffad25d1538a81b4831fd5ccd5658576658fc2 Binary files /dev/null and b/py311/lib/python3.11/site-packages/hydra/_internal/__pycache__/__init__.cpython-311.pyc differ diff --git a/py311/lib/python3.11/site-packages/hydra/_internal/__pycache__/callbacks.cpython-311.pyc b/py311/lib/python3.11/site-packages/hydra/_internal/__pycache__/callbacks.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ed1742f981d5c4f7261aae5762212cd35c194a9b Binary files /dev/null and b/py311/lib/python3.11/site-packages/hydra/_internal/__pycache__/callbacks.cpython-311.pyc differ diff --git a/py311/lib/python3.11/site-packages/hydra/_internal/__pycache__/config_loader_impl.cpython-311.pyc b/py311/lib/python3.11/site-packages/hydra/_internal/__pycache__/config_loader_impl.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a84d399190dcec7cd97eb990e80e0721b794198b Binary files /dev/null and b/py311/lib/python3.11/site-packages/hydra/_internal/__pycache__/config_loader_impl.cpython-311.pyc differ diff --git a/py311/lib/python3.11/site-packages/hydra/_internal/__pycache__/config_repository.cpython-311.pyc b/py311/lib/python3.11/site-packages/hydra/_internal/__pycache__/config_repository.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d31b5affe498ed9092001e9cab996188f464fcea Binary files /dev/null and b/py311/lib/python3.11/site-packages/hydra/_internal/__pycache__/config_repository.cpython-311.pyc differ diff --git a/py311/lib/python3.11/site-packages/hydra/_internal/__pycache__/config_search_path_impl.cpython-311.pyc b/py311/lib/python3.11/site-packages/hydra/_internal/__pycache__/config_search_path_impl.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6aa803766477030decb44057a58426bfc9febefb Binary files /dev/null and b/py311/lib/python3.11/site-packages/hydra/_internal/__pycache__/config_search_path_impl.cpython-311.pyc differ diff --git a/py311/lib/python3.11/site-packages/hydra/_internal/__pycache__/sources_registry.cpython-311.pyc b/py311/lib/python3.11/site-packages/hydra/_internal/__pycache__/sources_registry.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fdf90dcb832ada1443f2f376789cae266ebcab6b Binary files /dev/null and b/py311/lib/python3.11/site-packages/hydra/_internal/__pycache__/sources_registry.cpython-311.pyc differ diff --git a/py311/lib/python3.11/site-packages/hydra/_internal/callbacks.py b/py311/lib/python3.11/site-packages/hydra/_internal/callbacks.py new file mode 100644 index 0000000000000000000000000000000000000000..36226bc90d45582e918525b6ee774523c8a2d9b8 --- /dev/null +++ b/py311/lib/python3.11/site-packages/hydra/_internal/callbacks.py @@ -0,0 +1,65 @@ +# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved +import warnings +from typing import TYPE_CHECKING, Any, Optional + +from omegaconf import DictConfig, OmegaConf + +from hydra.types import TaskFunction + +if TYPE_CHECKING: + from hydra.core.utils import JobReturn + + +class Callbacks: + def __init__(self, config: Optional[DictConfig] = None) -> None: + self.callbacks = [] + from hydra.utils import instantiate + + if config is not None and OmegaConf.select(config, "hydra.callbacks"): + for params in config.hydra.callbacks.values(): + self.callbacks.append(instantiate(params)) + + def _notify(self, function_name: str, reverse: bool = False, **kwargs: Any) -> None: + callbacks = reversed(self.callbacks) if reverse else self.callbacks + for c in callbacks: + try: + getattr(c, function_name)(**kwargs) + except Exception as e: + warnings.warn( + f"Callback {type(c).__name__}.{function_name} raised {type(e).__name__}: {e}" + ) + + def on_run_start(self, config: DictConfig, **kwargs: Any) -> None: + self._notify(function_name="on_run_start", config=config, **kwargs) + + def on_run_end(self, config: DictConfig, **kwargs: Any) -> None: + self._notify(function_name="on_run_end", config=config, reverse=True, **kwargs) + + def on_multirun_start(self, config: DictConfig, **kwargs: Any) -> None: + self._notify(function_name="on_multirun_start", config=config, **kwargs) + + def on_multirun_end(self, config: DictConfig, **kwargs: Any) -> None: + self._notify( + function_name="on_multirun_end", reverse=True, config=config, **kwargs + ) + + def on_job_start( + self, config: DictConfig, *, task_function: TaskFunction, **kwargs: Any + ) -> None: + self._notify( + function_name="on_job_start", + config=config, + task_function=task_function, + **kwargs, + ) + + def on_job_end( + self, config: DictConfig, job_return: "JobReturn", **kwargs: Any + ) -> None: + self._notify( + function_name="on_job_end", + config=config, + job_return=job_return, + reverse=True, + **kwargs, + ) diff --git a/py311/lib/python3.11/site-packages/hydra/_internal/config_loader_impl.py b/py311/lib/python3.11/site-packages/hydra/_internal/config_loader_impl.py new file mode 100644 index 0000000000000000000000000000000000000000..bf89b0d2b12d70b412f298524d55a3a0a584a9f1 --- /dev/null +++ b/py311/lib/python3.11/site-packages/hydra/_internal/config_loader_impl.py @@ -0,0 +1,600 @@ +# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved +import copy +import os +import re +import sys +import warnings +from textwrap import dedent +from typing import Any, List, MutableSequence, Optional, Tuple + +from omegaconf import Container, DictConfig, OmegaConf, flag_override, open_dict +from omegaconf.errors import ( + ConfigAttributeError, + ConfigKeyError, + OmegaConfBaseException, +) + +from hydra._internal.config_repository import ( + CachingConfigRepository, + ConfigRepository, + IConfigRepository, +) +from hydra._internal.defaults_list import DefaultsList, create_defaults_list +from hydra.conf import ConfigSourceInfo +from hydra.core.config_loader import ConfigLoader +from hydra.core.config_search_path import ConfigSearchPath +from hydra.core.default_element import ResultDefault +from hydra.core.object_type import ObjectType +from hydra.core.override_parser.overrides_parser import OverridesParser +from hydra.core.override_parser.types import Override, ValueType +from hydra.core.utils import JobRuntime +from hydra.errors import ConfigCompositionException, MissingConfigException +from hydra.plugins.config_source import ConfigLoadError, ConfigResult, ConfigSource +from hydra.types import RunMode + +from .deprecation_warning import deprecation_warning + + +class ConfigLoaderImpl(ConfigLoader): + """ + Configuration loader + """ + + def __init__( + self, + config_search_path: ConfigSearchPath, + ) -> None: + self.config_search_path = config_search_path + self.repository = ConfigRepository(config_search_path=config_search_path) + + @staticmethod + def validate_sweep_overrides_legal( + overrides: List[Override], + run_mode: RunMode, + from_shell: bool, + ) -> None: + for x in overrides: + if x.is_sweep_override(): + if run_mode == RunMode.MULTIRUN: + if x.is_hydra_override(): + raise ConfigCompositionException( + "Sweeping over Hydra's configuration is not supported :" + f" '{x.input_line}'" + ) + elif run_mode == RunMode.RUN: + if x.value_type == ValueType.SIMPLE_CHOICE_SWEEP: + vals = "value1,value2" + if from_shell: + example_override = f"key=\\'{vals}\\'" + else: + example_override = f"key='{vals}'" + + msg = dedent( + f"""\ + Ambiguous value for argument '{x.input_line}' + 1. To use it as a list, use key=[value1,value2] + 2. To use it as string, quote the value: {example_override} + 3. To sweep over it, add --multirun to your command line""" + ) + raise ConfigCompositionException(msg) + else: + raise ConfigCompositionException( + f"Sweep parameters '{x.input_line}' requires --multirun" + ) + else: + assert False + + def _missing_config_error( + self, config_name: Optional[str], msg: str, with_search_path: bool + ) -> None: + def add_search_path() -> str: + descs = [] + for src in self.repository.get_sources(): + if src.provider != "schema": + descs.append(f"\t{repr(src)}") + lines = "\n".join(descs) + + if with_search_path: + return msg + "\nSearch path:" + f"\n{lines}" + else: + return msg + + raise MissingConfigException( + missing_cfg_file=config_name, message=add_search_path() + ) + + def ensure_main_config_source_available(self) -> None: + for source in self.get_sources(): + # if specified, make sure main config search path exists + if source.provider == "main": + if not source.available(): + if source.scheme() == "pkg": + if source.path == "": + msg = ( + "Primary config module is empty.\nPython requires" + " resources to be in a module with an __init__.py file" + ) + else: + msg = ( + f"Primary config module '{source.path}' not" + " found.\nCheck that it's correct and contains an" + " __init__.py file" + ) + else: + msg = ( + "Primary config directory not found.\nCheck that the" + f" config directory '{source.path}' exists and readable" + ) + + self._missing_config_error( + config_name=None, msg=msg, with_search_path=False + ) + + def load_configuration( + self, + config_name: Optional[str], + overrides: List[str], + run_mode: RunMode, + from_shell: bool = True, + validate_sweep_overrides: bool = True, + ) -> DictConfig: + try: + return self._load_configuration_impl( + config_name=config_name, + overrides=overrides, + run_mode=run_mode, + from_shell=from_shell, + validate_sweep_overrides=validate_sweep_overrides, + ) + except OmegaConfBaseException as e: + raise ConfigCompositionException().with_traceback(sys.exc_info()[2]) from e + + def _process_config_searchpath( + self, + config_name: Optional[str], + parsed_overrides: List[Override], + repo: CachingConfigRepository, + ) -> None: + if config_name is not None: + loaded = repo.load_config(config_path=config_name) + primary_config: Container + if loaded is None: + primary_config = OmegaConf.create() + else: + primary_config = loaded.config + else: + primary_config = OmegaConf.create() + + if not OmegaConf.is_dict(primary_config): + raise ConfigCompositionException( + f"primary config '{config_name}' must be a DictConfig, got" + f" {type(primary_config).__name__}" + ) + + def is_searchpath_override(v: Override) -> bool: + return v.get_key_element() == "hydra.searchpath" + + override = None + for v in parsed_overrides: + if is_searchpath_override(v): + override = v.value() + break + + searchpath = OmegaConf.select(primary_config, "hydra.searchpath") + if override is not None: + provider = "hydra.searchpath in command-line" + searchpath = override + else: + provider = "hydra.searchpath in main" + + def _err() -> None: + raise ConfigCompositionException( + f"hydra.searchpath must be a list of strings. Got: {searchpath}" + ) + + if searchpath is None: + return + + # validate hydra.searchpath. + # Note that we cannot rely on OmegaConf validation here because we did not yet merge with the Hydra schema node + if not isinstance(searchpath, MutableSequence): + _err() + for v in searchpath: + if not isinstance(v, str): + _err() + + new_csp = copy.deepcopy(self.config_search_path) + schema = new_csp.get_path().pop(-1) + assert schema.provider == "schema" + for sp in searchpath: + new_csp.append(provider=provider, path=sp) + new_csp.append("schema", "structured://") + repo.initialize_sources(new_csp) + + for source in repo.get_sources(): + if not source.available(): + warnings.warn( + category=UserWarning, + message=( + f"provider={source.provider}, path={source.path} is not" + " available." + ), + ) + + def _parse_overrides_and_create_caching_repo( + self, config_name: Optional[str], overrides: List[str] + ) -> Tuple[List[Override], CachingConfigRepository]: + parser = OverridesParser.create() + parsed_overrides = parser.parse_overrides(overrides=overrides) + caching_repo = CachingConfigRepository(self.repository) + self._process_config_searchpath(config_name, parsed_overrides, caching_repo) + return parsed_overrides, caching_repo + + def _load_configuration_impl( + self, + config_name: Optional[str], + overrides: List[str], + run_mode: RunMode, + from_shell: bool = True, + validate_sweep_overrides: bool = True, + ) -> DictConfig: + from hydra import __version__, version + + self.ensure_main_config_source_available() + parsed_overrides, caching_repo = self._parse_overrides_and_create_caching_repo( + config_name, overrides + ) + + if validate_sweep_overrides: + self.validate_sweep_overrides_legal( + overrides=parsed_overrides, run_mode=run_mode, from_shell=from_shell + ) + + defaults_list = create_defaults_list( + repo=caching_repo, + config_name=config_name, + overrides_list=parsed_overrides, + prepend_hydra=True, + skip_missing=run_mode == RunMode.MULTIRUN, + ) + + config_overrides = defaults_list.config_overrides + + cfg = self._compose_config_from_defaults_list( + defaults=defaults_list.defaults, repo=caching_repo + ) + + # Set config root to struct mode. + # Note that this will close any dictionaries (including dicts annotated as Dict[K, V]. + # One must use + to add new fields to them. + OmegaConf.set_struct(cfg, True) + + # The Hydra node should not be read-only even if the root config is read-only. + OmegaConf.set_readonly(cfg.hydra, False) + + # Apply command line overrides after enabling strict flag + ConfigLoaderImpl._apply_overrides_to_config(config_overrides, cfg) + app_overrides = [] + for override in parsed_overrides: + if override.is_hydra_override(): + cfg.hydra.overrides.hydra.append(override.input_line) + else: + cfg.hydra.overrides.task.append(override.input_line) + app_overrides.append(override) + + with open_dict(cfg.hydra): + cfg.hydra.runtime.choices.update(defaults_list.overrides.known_choices) + for key in cfg.hydra.job.env_copy: + cfg.hydra.job.env_set[key] = os.environ[key] + + cfg.hydra.runtime.version = __version__ + cfg.hydra.runtime.version_base = version.getbase() + cfg.hydra.runtime.cwd = os.getcwd() + + cfg.hydra.runtime.config_sources = [ + ConfigSourceInfo(path=x.path, schema=x.scheme(), provider=x.provider) + for x in caching_repo.get_sources() + ] + + if "name" not in cfg.hydra.job: + cfg.hydra.job.name = JobRuntime().get("name") + + cfg.hydra.job.override_dirname = get_overrides_dirname( + overrides=app_overrides, + kv_sep=cfg.hydra.job.config.override_dirname.kv_sep, + item_sep=cfg.hydra.job.config.override_dirname.item_sep, + exclude_keys=cfg.hydra.job.config.override_dirname.exclude_keys, + ) + cfg.hydra.job.config_name = config_name + + return cfg + + def load_sweep_config( + self, master_config: DictConfig, sweep_overrides: List[str] + ) -> DictConfig: + # Recreate the config for this sweep instance with the appropriate overrides + overrides = OmegaConf.to_container(master_config.hydra.overrides.hydra) + assert isinstance(overrides, list) + overrides = overrides + sweep_overrides + sweep_config = self.load_configuration( + config_name=master_config.hydra.job.config_name, + overrides=overrides, + run_mode=RunMode.RUN, + ) + + # Copy old config cache to ensure we get the same resolved values (for things + # like timestamps etc). Since `oc.env` does not cache environment variables + # (but the deprecated `env` resolver did), the entire config should be copied + OmegaConf.copy_cache(from_config=master_config, to_config=sweep_config) + + return sweep_config + + def get_search_path(self) -> ConfigSearchPath: + return self.config_search_path + + @staticmethod + def _apply_overrides_to_config(overrides: List[Override], cfg: DictConfig) -> None: + for override in overrides: + if override.package is not None: + raise ConfigCompositionException( + f"Override {override.input_line} looks like a config group" + f" override, but config group '{override.key_or_group}' does not" + " exist." + ) + + key = override.key_or_group + value = override.value() + try: + if override.is_delete(): + config_val = OmegaConf.select(cfg, key, throw_on_missing=False) + if config_val is None: + raise ConfigCompositionException( + f"Could not delete from config. '{override.key_or_group}'" + " does not exist." + ) + elif value is not None and value != config_val: + raise ConfigCompositionException( + "Could not delete from config. The value of" + f" '{override.key_or_group}' is {config_val} and not" + f" {value}." + ) + + last_dot = key.rfind(".") + with open_dict(cfg): + if last_dot == -1: + del cfg[key] + else: + node = OmegaConf.select(cfg, key[0:last_dot]) + del node[key[last_dot + 1 :]] + + elif override.is_add(): + if OmegaConf.select( + cfg, key, throw_on_missing=False + ) is None or isinstance(value, (dict, list)): + OmegaConf.update(cfg, key, value, merge=True, force_add=True) + else: + assert override.input_line is not None + raise ConfigCompositionException( + dedent( + f"""\ + Could not append to config. An item is already at '{override.key_or_group}'. + Either remove + prefix: '{override.input_line[1:]}' + Or add a second + to add or override '{override.key_or_group}': '+{override.input_line}' + """ + ) + ) + elif override.is_force_add(): + OmegaConf.update(cfg, key, value, merge=True, force_add=True) + else: + try: + OmegaConf.update(cfg, key, value, merge=True) + except (ConfigAttributeError, ConfigKeyError) as ex: + raise ConfigCompositionException( + f"Could not override '{override.key_or_group}'." + f"\nTo append to your config use +{override.input_line}" + ) from ex + except OmegaConfBaseException as ex: + raise ConfigCompositionException( + f"Error merging override {override.input_line}" + ).with_traceback(sys.exc_info()[2]) from ex + + def _load_single_config( + self, default: ResultDefault, repo: IConfigRepository + ) -> ConfigResult: + config_path = default.config_path + + assert config_path is not None + ret = repo.load_config(config_path=config_path) + assert ret is not None + + if not OmegaConf.is_config(ret.config): + raise ValueError( + f"Config {config_path} must be an OmegaConf config, got" + f" {type(ret.config).__name__}" + ) + + if not ret.is_schema_source: + schema = None + try: + schema_source = repo.get_schema_source() + cname = ConfigSource._normalize_file_name(filename=config_path) + schema = schema_source.load_config(cname) + except ConfigLoadError: + # schema not found, ignore + pass + + if schema is not None: + try: + url = "https://hydra.cc/docs/1.2/upgrades/1.0_to_1.1/automatic_schema_matching" + if "defaults" in schema.config: + raise ConfigCompositionException( + dedent( + f"""\ + '{config_path}' is validated against ConfigStore schema with the same name. + This behavior is deprecated in Hydra 1.1 and will be removed in Hydra 1.2. + In addition, the automatically matched schema contains a defaults list. + This combination is no longer supported. + See {url} for migration instructions.""" + ) + ) + else: + deprecation_warning( + dedent( + f"""\ + + '{config_path}' is validated against ConfigStore schema with the same name. + This behavior is deprecated in Hydra 1.1 and will be removed in Hydra 1.2. + See {url} for migration instructions.""" + ), + stacklevel=11, + ) + + # if primary config has a hydra node, remove it during validation and add it back. + # This allows overriding Hydra's configuration without declaring it's node + # in the schema of every primary config + hydra = None + hydra_config_group = ( + default.config_path is not None + and default.config_path.startswith("hydra/") + ) + config = ret.config + if ( + default.primary + and isinstance(config, DictConfig) + and "hydra" in config + and not hydra_config_group + ): + hydra = config.pop("hydra") + + merged = OmegaConf.merge(schema.config, config) + assert isinstance(merged, DictConfig) + + if hydra is not None: + with open_dict(merged): + merged.hydra = hydra + ret.config = merged + except OmegaConfBaseException as e: + raise ConfigCompositionException( + f"Error merging '{config_path}' with schema" + ) from e + + assert isinstance(merged, DictConfig) + + res = self._embed_result_config(ret, default.package) + if ( + not default.primary + and config_path != "hydra/config" + and isinstance(res.config, DictConfig) + and OmegaConf.select(res.config, "hydra.searchpath") is not None + ): + raise ConfigCompositionException( + f"In '{config_path}': Overriding hydra.searchpath is only supported" + " from the primary config" + ) + + return res + + @staticmethod + def _embed_result_config( + ret: ConfigResult, package_override: Optional[str] + ) -> ConfigResult: + package = ret.header["package"] + if package_override is not None: + package = package_override + + if package is not None and package != "": + cfg = OmegaConf.create() + OmegaConf.update(cfg, package, ret.config, merge=False) + ret = copy.copy(ret) + ret.config = cfg + + return ret + + def list_groups(self, parent_name: str) -> List[str]: + return self.get_group_options( + group_name=parent_name, results_filter=ObjectType.GROUP + ) + + def get_group_options( + self, + group_name: str, + results_filter: Optional[ObjectType] = ObjectType.CONFIG, + config_name: Optional[str] = None, + overrides: Optional[List[str]] = None, + ) -> List[str]: + if overrides is None: + overrides = [] + _, caching_repo = self._parse_overrides_and_create_caching_repo( + config_name, overrides + ) + return caching_repo.get_group_options(group_name, results_filter) + + def _compose_config_from_defaults_list( + self, + defaults: List[ResultDefault], + repo: IConfigRepository, + ) -> DictConfig: + cfg = OmegaConf.create() + with flag_override(cfg, "no_deepcopy_set_nodes", True): + for default in defaults: + loaded = self._load_single_config(default=default, repo=repo) + try: + cfg.merge_with(loaded.config) + except OmegaConfBaseException as e: + raise ConfigCompositionException( + f"In '{default.config_path}': {type(e).__name__} raised while" + f" composing config:\n{e}" + ).with_traceback(sys.exc_info()[2]) + + # # remove remaining defaults lists from all nodes. + def strip_defaults(cfg: Any) -> None: + if isinstance(cfg, DictConfig): + if cfg._is_missing() or cfg._is_none(): + return + with flag_override(cfg, ["readonly", "struct"], False): + if cfg._get_flag("HYDRA_REMOVE_TOP_LEVEL_DEFAULTS"): + cfg._set_flag("HYDRA_REMOVE_TOP_LEVEL_DEFAULTS", None) + cfg.pop("defaults", None) + + for _key, value in cfg.items_ex(resolve=False): + strip_defaults(value) + + strip_defaults(cfg) + + return cfg + + def get_sources(self) -> List[ConfigSource]: + return self.repository.get_sources() + + def compute_defaults_list( + self, + config_name: Optional[str], + overrides: List[str], + run_mode: RunMode, + ) -> DefaultsList: + parsed_overrides, caching_repo = self._parse_overrides_and_create_caching_repo( + config_name, overrides + ) + defaults_list = create_defaults_list( + repo=caching_repo, + config_name=config_name, + overrides_list=parsed_overrides, + prepend_hydra=True, + skip_missing=run_mode == RunMode.MULTIRUN, + ) + return defaults_list + + +def get_overrides_dirname( + overrides: List[Override], exclude_keys: List[str], item_sep: str, kv_sep: str +) -> str: + lines = [] + for override in overrides: + if override.key_or_group not in exclude_keys: + line = override.input_line + assert line is not None + lines.append(line) + + lines.sort() + ret = re.sub(pattern="[=]", repl=kv_sep, string=item_sep.join(lines)) + return ret diff --git a/py311/lib/python3.11/site-packages/hydra/_internal/config_repository.py b/py311/lib/python3.11/site-packages/hydra/_internal/config_repository.py new file mode 100644 index 0000000000000000000000000000000000000000..3beb5e43238bf0656e9abd79a02ea0c59d7af338 --- /dev/null +++ b/py311/lib/python3.11/site-packages/hydra/_internal/config_repository.py @@ -0,0 +1,366 @@ +# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved +import copy +from abc import ABC, abstractmethod +from dataclasses import dataclass +from textwrap import dedent +from typing import Dict, List, Optional, Tuple + +from omegaconf import ( + Container, + DictConfig, + ListConfig, + Node, + OmegaConf, + open_dict, + read_write, +) + +from hydra import version +from hydra.core.config_search_path import ConfigSearchPath +from hydra.core.object_type import ObjectType +from hydra.plugins.config_source import ConfigResult, ConfigSource + +from ..core.default_element import ConfigDefault, GroupDefault, InputDefault +from .deprecation_warning import deprecation_warning +from .sources_registry import SourcesRegistry + + +class IConfigRepository(ABC): + @abstractmethod + def get_schema_source(self) -> ConfigSource: + ... + + @abstractmethod + def load_config(self, config_path: str) -> Optional[ConfigResult]: + ... + + @abstractmethod + def group_exists(self, config_path: str) -> bool: + ... + + @abstractmethod + def config_exists(self, config_path: str) -> bool: + ... + + @abstractmethod + def get_group_options( + self, group_name: str, results_filter: Optional[ObjectType] = ObjectType.CONFIG + ) -> List[str]: + ... + + @abstractmethod + def get_sources(self) -> List[ConfigSource]: + ... + + @abstractmethod + def initialize_sources(self, config_search_path: ConfigSearchPath) -> None: + ... + + +class ConfigRepository(IConfigRepository): + config_search_path: ConfigSearchPath + sources: List[ConfigSource] + + def __init__(self, config_search_path: ConfigSearchPath) -> None: + self.initialize_sources(config_search_path) + + def initialize_sources(self, config_search_path: ConfigSearchPath) -> None: + self.sources = [] + for search_path in config_search_path.get_path(): + assert search_path.path is not None + assert search_path.provider is not None + scheme = self._get_scheme(search_path.path) + source_type = SourcesRegistry.instance().resolve(scheme) + source = source_type(search_path.provider, search_path.path) + self.sources.append(source) + + def get_schema_source(self) -> ConfigSource: + source = self.sources[-1] # should always be last + assert ( + source.__class__.__name__ == "StructuredConfigSource" + and source.provider == "schema" + ), "schema config source must be last" + return source + + def load_config(self, config_path: str) -> Optional[ConfigResult]: + source = self._find_object_source( + config_path=config_path, object_type=ObjectType.CONFIG + ) + ret = None + if source is not None: + ret = source.load_config(config_path=config_path) + # if this source is THE schema source, flag the result as coming from it. + ret.is_schema_source = ( + source.__class__.__name__ == "StructuredConfigSource" + and source.provider == "schema" + ) + + if ret is not None: + raw_defaults = self._extract_defaults_list(config_path, ret.config) + ret.defaults_list = self._create_defaults_list(config_path, raw_defaults) + + return ret + + def group_exists(self, config_path: str) -> bool: + return self._find_object_source(config_path, ObjectType.GROUP) is not None + + def config_exists(self, config_path: str) -> bool: + return self._find_object_source(config_path, ObjectType.CONFIG) is not None + + def get_group_options( + self, group_name: str, results_filter: Optional[ObjectType] = ObjectType.CONFIG + ) -> List[str]: + options: List[str] = [] + for source in self.sources: + if source.is_group(config_path=group_name): + options.extend( + source.list(config_path=group_name, results_filter=results_filter) + ) + return sorted(list(set(options))) + + def get_sources(self) -> List[ConfigSource]: + return self.sources + + def _find_object_source( + self, config_path: str, object_type: Optional[ObjectType] + ) -> Optional[ConfigSource]: + found_source = None + for source in self.sources: + if object_type == ObjectType.CONFIG: + if source.is_config(config_path): + found_source = source + break + elif object_type == ObjectType.GROUP: + if source.is_group(config_path): + found_source = source + break + else: + raise ValueError("Unexpected object_type") + return found_source + + @staticmethod + def _get_scheme(path: str) -> str: + idx = path.find("://") + if idx == -1: + return "file" + else: + return path[0:idx] + + def _split_group( + self, + group_with_package: str, + ) -> Tuple[str, Optional[str], Optional[str]]: + idx = group_with_package.find("@") + if idx == -1: + # group + group = group_with_package + package = None + else: + # group@package + group = group_with_package[0:idx] + package = group_with_package[idx + 1 :] + + package2 = None + if package is not None: + # if we have a package, break it down if it's a rename + idx = package.find(":") + if idx != -1: + package2 = package[idx + 1 :] + package = package[0:idx] + + return group, package, package2 + + def _create_defaults_list( + self, + config_path: str, + defaults: ListConfig, + ) -> List[InputDefault]: + def issue_deprecated_name_warning() -> None: + # DEPRECATED: remove in 1.2 + url = "https://hydra.cc/docs/1.2/upgrades/1.0_to_1.1/changes_to_package_header" + deprecation_warning( + message=dedent( + f"""\ + In {config_path}: Defaults List contains deprecated keyword _name_, see {url} + """ + ), + ) + + res: List[InputDefault] = [] + for item in defaults._iter_ex(resolve=False): + default: InputDefault + if isinstance(item, DictConfig): + if not version.base_at_least("1.2"): + old_optional = None + if len(item) > 1: + if "optional" in item: + old_optional = item.pop("optional") + keys = list(item.keys()) + + if len(keys) > 1: + raise ValueError( + f"In {config_path}: Too many keys in default item {item}" + ) + if len(keys) == 0: + raise ValueError(f"In {config_path}: Missing group name in {item}") + + key = keys[0] + assert isinstance(key, str) + config_group, package, _package2 = self._split_group(key) + keywords = ConfigRepository.Keywords() + self._extract_keywords_from_config_group(config_group, keywords) + + if not version.base_at_least("1.2"): + if not keywords.optional and old_optional is not None: + keywords.optional = old_optional + + node = item._get_node(key) + assert node is not None and isinstance(node, Node) + config_value = node._value() + + if not version.base_at_least("1.2"): + if old_optional is not None: + msg = dedent( + f""" + In {config_path}: 'optional: true' is deprecated. + Use 'optional {key}: {config_value}' instead. + Support for the old style is removed for Hydra version_base >= 1.2""" + ) + + deprecation_warning(msg) + + if config_value is not None and not isinstance( + config_value, (str, list) + ): + raise ValueError( + f"Unsupported item value in defaults : {type(config_value).__name__}." + " Supported: string or list" + ) + + if isinstance(config_value, list): + options = [] + for v in config_value: + vv = v._value() + if not isinstance(vv, str): + raise ValueError( + f"Unsupported item value in defaults : {type(vv).__name__}," + " nested list items must be strings" + ) + options.append(vv) + config_value = options + + if not version.base_at_least("1.2"): + if package is not None and "_name_" in package: + issue_deprecated_name_warning() + + default = GroupDefault( + group=keywords.group, + value=config_value, + package=package, + optional=keywords.optional, + override=keywords.override, + ) + + elif isinstance(item, str): + path, package, _package2 = self._split_group(item) + if not version.base_at_least("1.2"): + if package is not None and "_name_" in package: + issue_deprecated_name_warning() + + default = ConfigDefault(path=path, package=package) + else: + raise ValueError( + f"Unsupported type in defaults : {type(item).__name__}" + ) + res.append(default) + return res + + def _extract_defaults_list(self, config_path: str, cfg: Container) -> ListConfig: + empty = OmegaConf.create([]) + if not OmegaConf.is_dict(cfg): + return empty + assert isinstance(cfg, DictConfig) + with read_write(cfg): + with open_dict(cfg): + if not cfg._is_typed(): + defaults = cfg.pop("defaults", empty) + else: + # If node is a backed by Structured Config, flag it and temporarily keep the defaults list in. + # It will be removed later. + # This is addressing an edge case where the defaults list re-appears once the dataclass is used + # as a prototype during OmegaConf merge. + cfg._set_flag("HYDRA_REMOVE_TOP_LEVEL_DEFAULTS", True) + defaults = cfg.get("defaults", empty) + if not isinstance(defaults, ListConfig): + if isinstance(defaults, DictConfig): + type_str = "mapping" + else: + type_str = type(defaults).__name__ + raise ValueError( + f"Invalid defaults list in '{config_path}', defaults must be a list (got {type_str})" + ) + + return defaults + + @dataclass + class Keywords: + optional: bool = False + override: bool = False + group: str = "" + + @staticmethod + def _extract_keywords_from_config_group( + group: str, keywords: "ConfigRepository.Keywords" + ) -> None: + elements = group.split(" ") + group = elements[-1] + elements = elements[0:-1] + for idx, e in enumerate(elements): + if e == "optional": + keywords.optional = True + elif e == "override": + keywords.override = True + else: + break + keywords.group = group + + +class CachingConfigRepository(IConfigRepository): + def __init__(self, delegate: IConfigRepository): + # copy the underlying repository to avoid mutating it with initialize_sources() + self.delegate = copy.deepcopy(delegate) + self.cache: Dict[str, Optional[ConfigResult]] = {} + + def get_schema_source(self) -> ConfigSource: + return self.delegate.get_schema_source() + + def initialize_sources(self, config_search_path: ConfigSearchPath) -> None: + self.delegate.initialize_sources(config_search_path) + # not clearing the cache. + # For the use case this is used, the only thing in the cache is the primary config + # and we want to keep it even though we re-initialized the sources. + + def load_config(self, config_path: str) -> Optional[ConfigResult]: + cache_key = f"config_path={config_path}" + if cache_key in self.cache: + return self.cache[cache_key] + else: + ret = self.delegate.load_config(config_path=config_path) + self.cache[cache_key] = ret + return ret + + def group_exists(self, config_path: str) -> bool: + return self.delegate.group_exists(config_path=config_path) + + def config_exists(self, config_path: str) -> bool: + return self.delegate.config_exists(config_path=config_path) + + def get_group_options( + self, group_name: str, results_filter: Optional[ObjectType] = ObjectType.CONFIG + ) -> List[str]: + return self.delegate.get_group_options( + group_name=group_name, results_filter=results_filter + ) + + def get_sources(self) -> List[ConfigSource]: + return self.delegate.get_sources() diff --git a/py311/lib/python3.11/site-packages/hydra/_internal/config_search_path_impl.py b/py311/lib/python3.11/site-packages/hydra/_internal/config_search_path_impl.py new file mode 100644 index 0000000000000000000000000000000000000000..d0d7131f476866b0607656444b058393cb29c7d0 --- /dev/null +++ b/py311/lib/python3.11/site-packages/hydra/_internal/config_search_path_impl.py @@ -0,0 +1,98 @@ +# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved +from typing import List, MutableSequence, Optional, Union + +from hydra.core.config_search_path import ( + ConfigSearchPath, + SearchPathElement, + SearchPathQuery, +) + + +class ConfigSearchPathImpl(ConfigSearchPath): + config_search_path: List[SearchPathElement] + + def __init__(self) -> None: + self.config_search_path = [] + + def get_path(self) -> MutableSequence[SearchPathElement]: + return self.config_search_path + + def find_last_match(self, reference: SearchPathQuery) -> int: + return self.find_match(reference, reverse=True) + + def find_first_match(self, reference: SearchPathQuery) -> int: + return self.find_match(reference, reverse=False) + + def find_match(self, reference: SearchPathQuery, reverse: bool) -> int: + p = self.config_search_path + if reverse: + iterator = zip(reversed(range(len(p))), reversed(p)) + else: + iterator = zip(range(len(p)), p) + for idx, sp in iterator: + has_prov = reference.provider is not None + has_path = reference.path is not None + if has_prov and has_path: + if reference.provider == sp.provider and reference.path == sp.path: + return idx + elif has_prov: + if reference.provider == sp.provider: + return idx + elif has_path: + if reference.path == sp.path: + return idx + else: + assert False + return -1 + + def append( + self, provider: str, path: str, anchor: Optional[SearchPathQuery] = None + ) -> None: + if anchor is None: + self.config_search_path.append(SearchPathElement(provider, path)) + else: + if isinstance(anchor, str): + anchor = SearchPathQuery(anchor, None) + + idx = self.find_last_match(anchor) + if idx != -1: + self.config_search_path.insert( + idx + 1, SearchPathElement(provider, path) + ) + else: + self.append(provider, path, anchor=None) + + def prepend( + self, + provider: str, + path: str, + anchor: Optional[Union[SearchPathQuery, str]] = None, + ) -> None: + """ + Prepends to the search path. + Note, this currently only takes effect if called before the ConfigRepository is instantiated. + + :param provider: who is providing this search path, can be Hydra, + the @hydra.main() function, or individual plugins or libraries. + :param path: path element, can be a file system path or a package path (For example pkg://hydra.conf) + :param anchor: if string, acts as provider. if SearchPath can be used to match against provider and / or path + """ + if anchor is None: + self.config_search_path.insert(0, SearchPathElement(provider, path)) + else: + if isinstance(anchor, str): + anchor = SearchPathQuery(anchor, None) + + idx = self.find_first_match(anchor) + if idx != -1: + if idx > 0: + self.config_search_path.insert( + idx, SearchPathElement(provider, path) + ) + else: + self.prepend(provider, path, None) + else: + self.prepend(provider, path, None) + + def __str__(self) -> str: + return str(self.config_search_path) diff --git a/py311/lib/python3.11/site-packages/hydra/_internal/core_plugins/bash_completion.py b/py311/lib/python3.11/site-packages/hydra/_internal/core_plugins/bash_completion.py new file mode 100644 index 0000000000000000000000000000000000000000..6e59ac5db9a32ec6901f4a11fbcd2206b33c1b52 --- /dev/null +++ b/py311/lib/python3.11/site-packages/hydra/_internal/core_plugins/bash_completion.py @@ -0,0 +1,98 @@ +# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved +import logging +import os +import sys +from typing import Optional + +from hydra.plugins.completion_plugin import CompletionPlugin + +log = logging.getLogger(__name__) + + +class BashCompletion(CompletionPlugin): + def install(self) -> None: + # Record the old rule for uninstalling + script = ( + f"export _HYDRA_OLD_COMP=$(complete -p {self._get_exec()} 2> /dev/null)\n" + ) + script += """hydra_bash_completion() +{ + words=($COMP_LINE) + if [ "${words[0]}" == "python" ]; then + if (( ${#words[@]} < 2 )); then + return + fi + file_path=$(pwd)/${words[1]} + if [ ! -f "$file_path" ]; then + return + fi + grep "@hydra.main" $file_path -q + helper="${words[0]} ${words[1]}" + else + helper="${words[0]}" + true + fi + + EXECUTABLE=($(command -v $helper)) + if [ "$HYDRA_COMP_DEBUG" == "1" ]; then + printf "EXECUTABLE_FIRST='${EXECUTABLE[0]}'\\n" + fi + if ! [ -x "${EXECUTABLE[0]}" ]; then + false + fi + + if [ $? == 0 ]; then + choices=$( COMP_POINT=$COMP_POINT COMP_LINE=$COMP_LINE $helper -sc query=bash) + word=${words[$COMP_CWORD]} + + if [ "$HYDRA_COMP_DEBUG" == "1" ]; then + printf "\\n" + printf "COMP_LINE='$COMP_LINE'\\n" + printf "COMP_POINT='$COMP_POINT'\\n" + printf "Word='$word'\\n" + printf "Output suggestions:\\n" + printf "\\t%s\\n" ${choices[@]} + fi + COMPREPLY=($( compgen -o nospace -o default -W "$choices" -- "$word" )); + fi +} + +COMP_WORDBREAKS=${COMP_WORDBREAKS//=} +COMP_WORDBREAKS=$COMP_WORDBREAKS complete -o nospace -o default -F hydra_bash_completion """ + print(script + self._get_exec()) + + def uninstall(self) -> None: + print("unset hydra_bash_completion") + print(os.environ.get("_HYDRA_OLD_COMP", "")) + print("unset _HYDRA_OLD_COMP") + + @staticmethod + def provides() -> str: + return "bash" + + def query(self, config_name: Optional[str]) -> None: + line = os.environ["COMP_LINE"] + # key = os.environ["COMP_POINT "] if "COMP_POINT " in os.environ else len(line) + + # if key == "": + # key = 0 + # if isinstance(key, str): + # key = int(key) + + # currently key is ignored. + line = self.strip_python_or_app_name(line) + print(" ".join(self._query(config_name=config_name, line=line))) + + @staticmethod + def help(command: str) -> str: + assert command in ["install", "uninstall"] + return f'eval "$({{}} -sc {command}=bash)"' + + @staticmethod + def _get_exec() -> str: + if sys.argv[0].endswith(".py"): + return "python" + else: + # Running as an installed app (setuptools entry point) + executable = os.path.basename(sys.argv[0]) + return executable diff --git a/py311/lib/python3.11/site-packages/hydra/_internal/defaults_list.py b/py311/lib/python3.11/site-packages/hydra/_internal/defaults_list.py new file mode 100644 index 0000000000000000000000000000000000000000..b4b0d92c9503f28f6954a021cd9cf43c0b9ac8c5 --- /dev/null +++ b/py311/lib/python3.11/site-packages/hydra/_internal/defaults_list.py @@ -0,0 +1,803 @@ +# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved + +import copy +import os +import warnings +from dataclasses import dataclass, field +from textwrap import dedent +from typing import Callable, Dict, List, Optional, Set, Tuple, Union + +from omegaconf import DictConfig, OmegaConf + +from hydra import MissingConfigException, version +from hydra._internal.config_repository import IConfigRepository +from hydra.core.config_store import ConfigStore +from hydra.core.default_element import ( + ConfigDefault, + DefaultsTreeNode, + GroupDefault, + InputDefault, + ResultDefault, + VirtualRoot, +) +from hydra.core.object_type import ObjectType +from hydra.core.override_parser.types import Override +from hydra.errors import ConfigCompositionException + +from .deprecation_warning import deprecation_warning + +cs = ConfigStore.instance() + +cs.store(name="_dummy_empty_config_", node={}, provider="hydra") + + +@dataclass +class Deletion: + name: Optional[str] + used: bool = field(default=False, compare=False) + + +@dataclass +class OverrideMetadata: + external_override: bool + containing_config_path: Optional[str] = None + used: bool = False + relative_key: Optional[str] = None + + +@dataclass +class Overrides: + override_choices: Dict[str, Optional[Union[str, List[str]]]] + override_metadata: Dict[str, OverrideMetadata] + + append_group_defaults: List[GroupDefault] + config_overrides: List[Override] + + known_choices: Dict[str, Optional[str]] + known_choices_per_group: Dict[str, Set[str]] + + deletions: Dict[str, Deletion] + + def __init__(self, repo: IConfigRepository, overrides_list: List[Override]) -> None: + self.override_choices = {} + self.override_metadata = {} + self.append_group_defaults = [] + self.config_overrides = [] + self.deletions = {} + + self.known_choices = {} + self.known_choices_per_group = {} + + for override in overrides_list: + if override.is_sweep_override(): + continue + is_group = repo.group_exists(override.key_or_group) + value = override.value() + is_dict = isinstance(override.value(), dict) + if is_dict or not is_group: + self.config_overrides.append(override) + elif override.is_force_add(): + # This could probably be made to work if there is a compelling use case. + raise ConfigCompositionException( + f"force-add of config groups is not supported: '{override.input_line}'" + ) + elif override.is_delete(): + key = override.get_key_element()[1:] + value = override.value() + if value is not None and not isinstance(value, str): + raise ValueError( + f"Config group override deletion value must be a string : {override}" + ) + + self.deletions[key] = Deletion(name=value) + + elif not isinstance(value, (str, list)): + raise ValueError( + f"Config group override must be a string or a list. Got {type(value).__name__}" + ) + elif override.is_add(): + self.append_group_defaults.append( + GroupDefault( + group=override.key_or_group, + package=override.package, + value=value, + external_append=True, + ) + ) + else: + key = override.get_key_element() + self.override_choices[key] = value + self.override_metadata[key] = OverrideMetadata(external_override=True) + + def add_override(self, parent_config_path: str, default: GroupDefault) -> None: + assert default.override + key = default.get_override_key() + if key not in self.override_choices: + self.override_choices[key] = default.value + self.override_metadata[key] = OverrideMetadata( + external_override=False, + containing_config_path=parent_config_path, + relative_key=default.get_relative_override_key(), + ) + + def is_overridden(self, default: InputDefault) -> bool: + if isinstance(default, GroupDefault): + return default.get_override_key() in self.override_choices + + return False + + def override_default_option(self, default: GroupDefault) -> None: + key = default.get_override_key() + if key in self.override_choices: + if isinstance(default, GroupDefault): + default.value = self.override_choices[key] + default.config_name_overridden = True + self.override_metadata[key].used = True + + def ensure_overrides_used(self) -> None: + for key, meta in self.override_metadata.items(): + if not meta.used: + group = key.split("@")[0] + choices = ( + self.known_choices_per_group[group] + if group in self.known_choices_per_group + else set() + ) + + if len(choices) > 1: + msg = ( + f"Could not override '{key}'." + f"\nDid you mean to override one of {', '.join(sorted(list(choices)))}?" + ) + elif len(choices) == 1: + msg = ( + f"Could not override '{key}'." + f"\nDid you mean to override {copy.copy(choices).pop()}?" + ) + elif len(choices) == 0: + msg = f"Could not override '{key}'. No match in the defaults list." + else: + assert False + + if meta.containing_config_path is not None: + msg = f"In '{meta.containing_config_path}': {msg}" + + if meta.external_override: + msg += f"\nTo append to your default list use +{key}={self.override_choices[key]}" + + raise ConfigCompositionException(msg) + + def ensure_deletions_used(self) -> None: + for key, deletion in self.deletions.items(): + if not deletion.used: + desc = f"{key}={deletion.name}" if deletion.name is not None else key + msg = f"Could not delete '{desc}'. No match in the defaults list" + raise ConfigCompositionException(msg) + + def set_known_choice(self, default: InputDefault) -> None: + if isinstance(default, GroupDefault): + key = default.get_override_key() + if key not in self.known_choices: + self.known_choices[key] = default.get_name() + else: + prev = self.known_choices[key] + if default.get_name() != prev: + raise ConfigCompositionException( + f"Multiple values for {key}." + f" To override a value use 'override {key}: {prev}'" + ) + + group = default.get_group_path() + if group not in self.known_choices_per_group: + self.known_choices_per_group[group] = set() + self.known_choices_per_group[group].add(key) + + def is_deleted(self, default: InputDefault) -> bool: + if not isinstance(default, GroupDefault): + return False + key = default.get_override_key() + if key in self.deletions: + deletion = self.deletions[key] + if deletion.name is None: + return True + else: + return deletion.name == default.get_name() + return False + + def delete(self, default: InputDefault) -> None: + assert isinstance(default, GroupDefault) + default.deleted = True + + key = default.get_override_key() + self.deletions[key].used = True + + +@dataclass +class DefaultsList: + defaults: List[ResultDefault] + defaults_tree: DefaultsTreeNode + config_overrides: List[Override] + overrides: Overrides + + +def _validate_self( + containing_node: InputDefault, + defaults: List[InputDefault], + has_config_content: bool, +) -> bool: + # check that self is present only once + has_self = False + has_non_override = False + for d in defaults: + if not d.is_override(): + has_non_override = True + if d.is_self(): + if has_self: + raise ConfigCompositionException( + f"Duplicate _self_ defined in {containing_node.get_config_path()}" + ) + has_self = True + + if not has_self and has_non_override or len(defaults) == 0: + # This check is here to make the migration from Hydra 1.0 to Hydra 1.1 smoother and should be removed in 1.2 + # The warning should be removed in 1.2 + if containing_node.primary and has_config_content and has_non_override: + msg = ( + f"In '{containing_node.get_config_path()}': Defaults list is missing `_self_`. " + f"See https://hydra.cc/docs/1.2/upgrades/1.0_to_1.1/default_composition_order for more information" + ) + if os.environ.get("SELF_WARNING_AS_ERROR") == "1": + raise ConfigCompositionException(msg) + warnings.warn(msg, UserWarning) + defaults.append(ConfigDefault(path="_self_")) + + return not has_self + + +def update_package_header(repo: IConfigRepository, node: InputDefault) -> None: + if node.is_missing(): + return + # This loads the same config loaded in _create_defaults_tree + # To avoid loading it twice, the repo implementation is expected to cache loaded configs + loaded = repo.load_config(config_path=node.get_config_path()) + if loaded is not None: + node.set_package_header(loaded.header["package"]) + + +def _expand_virtual_root( + repo: IConfigRepository, + root: DefaultsTreeNode, + overrides: Overrides, + skip_missing: bool, +) -> DefaultsTreeNode: + children: List[Union[DefaultsTreeNode, InputDefault]] = [] + assert root.children is not None + for d in reversed(root.children): + assert isinstance(d, InputDefault) + new_root = DefaultsTreeNode(node=d, parent=root) + d.update_parent("", "") + + subtree = _create_defaults_tree_impl( + repo=repo, + root=new_root, + is_root_config=d.primary, + skip_missing=skip_missing, + interpolated_subtree=False, + overrides=overrides, + ) + if subtree.children is None: + children.append(d) + else: + children.append(subtree) + + if len(children) > 0: + root.children = list(reversed(children)) + + return root + + +def _check_not_missing( + repo: IConfigRepository, + default: InputDefault, + skip_missing: bool, +) -> bool: + path = default.get_config_path() + if path.endswith("???"): + if skip_missing: + return True + if isinstance(default, GroupDefault): + group_path = default.get_group_path() + override_key = default.get_override_key() + options = repo.get_group_options( + group_path, + results_filter=ObjectType.CONFIG, + ) + opt_list = "\n".join(["\t" + x for x in options]) + msg = dedent( + f"""\ + You must specify '{override_key}', e.g, {override_key}=